Skip to content

docs: cut filler from README and docs site - #786

Merged
thebuilder merged 11 commits into
mainfrom
feat/documentation-cleanup-7907fc
Aug 24, 2026
Merged

docs: cut filler from README and docs site#786
thebuilder merged 11 commits into
mainfrom
feat/documentation-cleanup-7907fc

Conversation

@thebuilder

@thebuilder thebuilder commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Pass over every prose file in the repo to remove AI-writing tells and say what the library does rather than how it feels.

What changed

README.md got the most work:

  • Feature list: dropped the emoji and the puffery. "🪝 Hooks or Component API - With useInView and useOnInView it's easier than ever to monitor elements" is now "Hooks or component API - useInView for React state, useOnInView for callbacks, <InView> for render props and wrapper elements."
  • Options table: the root row was 45 words of MDN boilerplate. Now one sentence. Same treatment for skip, initialInView, fallbackInView, children, and the test-utils method table.
  • Cut "In order to", "utilize", "makes it easy", "allows you to", "This is still a very new addition", "Browser support is excellent", "just a simple but powerful tool".
  • Headings to sentence case, one em dash removed, the 🧪 markers replaced with the word "Experimental".

Docs site:

  • overview.mdx: "Built for production React" was four bullets of marketing ("Native and efficient", "Testable at the right layer"). Rewritten as "What you get" with actual mechanisms and numbers. Em dashes in the next-steps list gone.
  • ssr.mdx: had real redundancy. "initialInView controls the pre-observer render; it does not create an observer" appeared three separate times, plus an orphan paragraph restating fallbackInView vs defaultFallbackInView without backticks. One statement each now, 20 lines shorter.
  • configuration.mdx: unpacked the colon-and-semicolon pileup in "Choose where to observe", dropped a duplicated "root must be an ancestor" sentence.
  • core-apis.mdx: removed "React surface" (used twice).
  • intersection-observer-v2.mdx: dropped a duplicated closing line about the mock and occlusion.
  • Testing guides: three em dashes removed, dense sentences split.

CONTRIBUTING.md / SECURITY.md: "I'm thrilled that you're interested in contributing" is gone, headings to sentence case, "Please ensure that your changes are formatted" became "Format your changes".

Fixes found along the way

  • Broken indentation in the README render-props code sample.
  • Typos: explictly, intersecing, "in test files were you actively import", "Ref's from useRef needs to have".
  • British specialised in an otherwise US-spelled doc set (behavior, behaviour inconsistency).

Reviewer notes

  • One anchor changed. Renaming ## Stop, pause, or choose a callback API to ## Stop or pause observation changed its slug, so the referring link in core-apis.mdx is updated to match. I grepped for the other renamed headings (Hydration considerations, Choose a test boundary, Start simple) and nothing links to them.
  • Content, not just style. The README option descriptions were rewritten rather than trimmed, so they're worth reading for accuracy. Same for the overview.mdx "What you get" bullets, which now make specific claims (shared observer instances, ~1.15kB gzipped) that should match reality.
  • pnpm --filter docs build passes, 112 pages.

Rewrite the prose across the README, docs site, CONTRIBUTING, and SECURITY to
say what the library does instead of how it feels.

- README: drop the emoji and puffery from the feature list, replace the
  MDN-boilerplate option descriptions with one-sentence versions, and rewrite
  the testing and polyfill sections in plain language.
- overview.mdx: replace the "Built for production React" marketing bullets with
  concrete mechanisms and numbers.
- ssr.mdx: remove three copies of the same initialInView caveat and an orphan
  paragraph restating fallbackInView vs defaultFallbackInView.
- configuration.mdx, core-apis.mdx, testing guides: split dense sentences,
  remove em dashes, drop "React surface" and other jargon.
- CONTRIBUTING, SECURITY: sentence-case headings, trim the filler.

Also fixes issues found along the way: broken indentation in the render-props
sample, the typos "explictly", "intersecing" and "test files were you actively
import", and the British "specialised" in an otherwise US-spelled doc set.

Renaming the "Stop, pause, or choose a callback API" heading changed its anchor,
so the link in core-apis.mdx is updated to match. Docs site builds clean.
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-intersection-observer Ready Ready Preview Aug 24, 2026 11:03am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
react-intersection-observer-storybook Skipped Skipped Aug 24, 2026 11:03am

@pkg-pr-new

pkg-pr-new Bot commented Aug 22, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/react-intersection-observer@786

commit: f606ad4

The landing page was in better shape than the markdown, so this is a smaller
pass over the copy that was still doing marketing instead of explaining.

- The demo feed teasers were the worst of it: "Design systems that travel",
  "A quiet note on shipping", "A scroll worth observing". Nine labels of
  evocative filler that would read identically in any other project. They are
  scroll filler, so number them. That also makes scroll position legible in a
  demo whose whole point is scrolling.
- "Ideal for analytics, prefetching, or logging" becomes "Use it for".
- The closing headline "Add a kilobyte. Ship the viewport." had a second half
  that does not mean anything. Now "Add a kilobyte. Know what is on screen.",
  which echoes the hero.
- Page title dropped a duplicate "for React"; the product name already says it.
- Site description traded "lightweight" for what the library actually does.
- Em dashes out of the section-divider comments and CSS, per house style.

Left the hero, feature grid, and section headings alone. They already make
specific claims and have a voice.
The page gave impressions a full interactive section but never showed lazy
loading, which is one of the main reasons people reach for this library. It
appeared once, as a hyphenated word in the hero lead.

Adds a section between the API scrollspy and the impression strip:

- Six tiles that reserve their space, then mount their <img> only once the
  observer reports them within 200px of the viewport. No image element exists
  in the DOM before that, so the deferral is real rather than a CSS trick.
- A counter showing how many have been requested, plus the useInView call that
  drives it.
- A note pointing plain images at loading="lazy" instead, so the section does
  not oversell the observer for cases the platform already handles.

The artwork is inline SVG data URIs. A tile that has not been reached costs
nothing and the page pulls no extra files.

Also leads with the use cases now that they have somewhere to point:

- Hero lead opens with "Reveal on scroll, lazy-load images, track impressions,
  build infinite lists" before the technical framing.
- README intro names scroll animations, lazy loading, impressions, and
  infinite scroll.
- Site description swaps "lightweight" for those same use cases, which is also
  closer to what people search for.

The index badge overlays the frame, so it needs to read against the empty
placeholder and the loaded artwork both. It uses the theme foreground when
deferred and white once loaded; a single colour only worked for one of them.

Verified in the browser at 375, 768, and 1280, in both themes: deferred state
holds 0/6 with no img elements, loading flips all six, the code panel does not
overflow its column, and the page never scrolls horizontally.
@thebuilder thebuilder changed the title docs: cut AI-generated filler from README and docs site docs: cut filler from README and docs site Aug 22, 2026
You don't need the effect. An empty list puts the sentinel inside the
viewport, so the observer already asks for the first page. The effect was a
second copy of the fetch, the error handling, and the loading flag, racing the
observer for the same request. The `loading: true` initial state only existed
to referee that race, so it goes too.

Four states become three. `loading` and `error` booleans could both be set at
once, which is not a state this component has; one `status` union removes it
and lets the button derive its own label. The `useCallback` goes as well: the
hook reads `onChange` from a ref, so a plain function is enough.

The two buttons merge into one that says "Try again" after a failure.

Verified against the browser's own IntersectionObserver, not the mock, since
the mock does not replay state to a newly created observer and hides the
behavior this recipe depends on. Six cases: first page loads with no effect,
a short page keeps filling, each page is requested exactly once, observation
stops on the last page, the button recovers from an error, and a failure does
not turn into a scroll-retry loop.

The re-arm is worth knowing about. Flipping `skip` drops and recreates the
observer, which is what lets a page too short to push the sentinel out of view
keep loading. The prose now says so instead of describing `skip` as only a
duplicate-request guard.
`void` was there to quiet a floating-promise lint rule, and `async` promised a
result nobody awaits. `loadMore` now starts the request and returns nothing,
so the observer calls `loadMore()` and the button takes `onClick={loadMore}`
directly.

The two-argument `.then` is deliberate. `.catch` after it would also swallow a
bug thrown by the state updates in the success path and report it to the user
as a failed request.

Same slop in the lazy-loading section I added two commits ago, caught while
looking: LazyTile reported visibility to its parent through a useEffect on
`inView`, which is the effect the recipe rewrite just removed. It uses
`onChange` now. That fires exactly once under `triggerOnce`, so the parent
counts with a number instead of an id array with an includes() dedupe guard,
and the useCallback around it goes too.

Verified against the browser's own observer, since the docs preview pane was
hidden and a hidden page delivers no intersections at all: the recipe still
passes all seven cases, and the tiles start deferred with no img in the DOM,
count to exactly six on scroll, and do not double-count when scrolled away
and back.
Yes, it is the right hook, and for a better reason than ergonomics.

The hand-rolled status had a real bug. When `loadPage` resolves from a warm
cache without ever yielding, React batches the update that sets the loading
flag together with the one that clears it. `skip` never changes, so the
observer never re-arms and the list stops after one page. Measured on the same
input: the status version requests [0], this one requests [0, 1, 2, 3, 4].

`isPending` is raised by React when `startTransition` runs and lowered when the
awaited work settles, so it renders either way. It also removes the
`setLoading(false)` that has to be repeated on every exit path, and marking the
append as a transition keeps a list of hundreds of rows from blocking a click.

It also answers the floating-promise objection properly rather than by
deleting `async`: `startTransition` awaits the function it is given, so the
async work now has an owner.

Async transitions need React 19, so the recipe carries a note telling React 18
readers to track the status themselves.

Verified against the browser's own observer, eight cases: first page with no
effect, a short page keeps filling, a warm cache keeps filling, each page
requested exactly once, observation stops on the last page, the live region
announces pending then failure, the button recovers, and a failure does not
become a scroll-retry loop.
Cuts the tells from the explanation. "And that is the point" was patting
itself on the back. "React raises isPending and lowers it" dressed up set and
clear. "Marking the append as a transition is worth it on its own:" leaned on
a colon to join two thoughts that wanted to be one sentence. "A hand-rolled
loading boolean is also less reliable here" hedged about something that is
simply broken, so it now says breaks.

Also answers the obvious question about the try/catch, since the transition
looks like it should handle the error itself. It does not. useTransition
returns isPending and startTransition, and nothing else. Measured what an
uncaught throw actually does: it reaches the nearest error boundary, the
boundary swaps in its fallback, and the list unmounts with every page already
loaded inside it. Failing on page five would discard pages one through four.
The catch is what keeps the rows on screen and leaves the button as a retry.
345 words down to 94. Most of it was arguing with an imagined reviewer rather
than telling a reader anything: why there is no effect, why a hand-rolled
loading boolean would be worse, why the try/catch has to stay, why the button
earns its place. That is a record of how the example was arrived at, and none
of it helps someone reading the example.

What survives is the part a reader cannot infer from the code. The sentinel
loads the first page. Flipping `skip` recreates the observer, which is what
keeps a short page filling. Tune `rootMargin`. Keep the button.

The other recipes on the page close in 53 to 85 words. This one was four times
longer than any of them.
"Keep the button" answers an objection the reader never made. Nobody proposed
removing it; it is right there in the example. The rule on its own carries the
point, so the sentence is now just "Scrolling should never be the only way to
load more."

The caption above the code had the same problem, telling the reader to "keep"
a button they have not added yet. It says add.
Swept the rest of the docs for prose that argues with a reader who has not
said anything. Two hits, both milder than the InfiniteList case.

configuration.mdx warned that `scrollMargin` is not a substitute for
`rootMargin` in the section on where to observe, having already drawn that
distinction 30 lines earlier where `rootMargin` is introduced. The second pass
now only carries what is new, the syntax and the gotchas.

The v2 guide had a heading, "Test it deliberately", where deliberately is a
judgement rather than a description. It says "Test it in a real browser",
which is the actual advice. Nothing links to the old anchor.

The rest came back clean. README and CONTRIBUTING have no instances. The
"keep" phrasings elsewhere are ordinary instructions about the reader's own
code, and the "instead of" ones are real either/or choices, Browser Mode
against jsdom and the mock against real layout. Section lengths now run 60 to
145 words with no outlier.
Three sentences defined something by naming a category it does not belong to.
Each one assumed the reader was mid-mistake, and none of them said anything
actionable, since the real advice was always in the sentences around them.

"A fallback is not a loading strategy" was a topic sentence for a paragraph
that already gives the advice. Deleted; the paragraph reads better without it.

"Reach for it when a scroller inside the root clips the target, not when you
want to adjust the viewport" both repeated the sentence before it and
corrected a reader who had not done anything. `scrollMargin` is now described
once, and the section on where to observe is where you find out when to use
it.

"It is not a layout ratio" guarded a genuine trap, since `mockAllIsIntersecting(0.3)`
does look like thirty percent visible. Naming what the number does select,
which of your configured thresholds the observer crossed, closes the same trap
without the correction.

Left the README FAQ line about `root` not being the viewport. That section
answers a reader who has already hit the problem and gone looking, so telling
them what is not true is the answer they came for.
@vercel
vercel Bot temporarily deployed to Preview – react-intersection-observer-storybook August 24, 2026 11:02 Inactive
@thebuilder
thebuilder marked this pull request as ready for review August 24, 2026 13:40
@thebuilder
thebuilder merged commit a0877f6 into main Aug 24, 2026
10 checks passed
@thebuilder
thebuilder deleted the feat/documentation-cleanup-7907fc branch August 24, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant