Repository navigation
Conversation
`.use()` called one query hook per method call in the chain and skipped it when the target of a call was empty. The number of hooks therefore depended on the chain and on intermediate results. React aborted with "Rendered more hooks than during the previous render." when, between two renders, the target of a chain was set, an `undefined` result in the chain became available or a ghost with a different chain was passed (e.g. a plain value from `asGhostProps()` vs. a ghost). All queries of a chain now go through a single `useSuspenseQueries()`, whose hooks do not depend on the number of queries. The chain is walked once against the query cache to find the queries needed so far and once against the hook results to compute the value. Query keys, sequential fetching, options, error boundaries and target auto invalidation behave as before. Transforms are memoized via a ref instead of one `useMemo` per chain item.
`ghost?.use()` skips the hook while the ghost is missing, before ghostmaker is involved. `optionalGhost(ghost).use()` always calls the same hooks and resolves to `undefined` if no ghost is given.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.use()now calls the same hooks for every ghost. Before, it called one query hook per method call in the chain and skipped it when the target of a call was empty. React aborted with "Rendered more hooks than during the previous render." when, between two renders:makeGhost(maybeProject).getDetailed().use()),undefinedresult in the chain became available (e.g. after invalidation),asGhostProps()vs. a ghost likeContractGhost.ofId(id).optionalGhost(ghost)replacesghost?.use(). Optional chaining skips the hook in JavaScript before ghostmaker is involved, so a library fix alone does not cover it.optionalGhost(ghost).use()always calls the hook and resolves toundefinedifghostisundefinedornull.How the hooks stay fixed
All queries of a chain go through a single
useSuspenseQueries(). Its hooks do not depend on the number of queries (checked inuseQueriesof@tanstack/react-query5.90.20).useGhostChain()walks the chain twice per render:useSuspenseQueries(), to compute the value.Queries are still fetched one after another, one suspension per step as before. Unchanged: query keys, options passed to every query, errors thrown to the error boundary (also on failed refetches with data), target auto invalidation.
Notes for reviewers
transform()results are memoized in a ref (per chain index, compared withObject.is) instead of oneuseMemoper chain item, since the number ofuseMemocalls would again depend on the chain.useEffect(..., [target]).maybeGhostwas considered, butMaybeReactGhostalready means "ghost or plain value", so the function is calledoptionalGhost.Test plan
pnpm testinpackages/react-ghostmaker(tsc, lint, build, vitest, licenses) passes locally, 121 tests (before: 115)makeGhost.test.tsx("Hook order", 3 tests) andoptionalGhost.test.tsx("keeps the hook order when the ghost is set or removed") fail with "Rendered more hooks than during the previous render." on the previoususeGhostChainand pass with this changeoptionalGhost.test-d.ts