A tag with a falsy ID is never invalidated
Summary
When a tag has a falsy ID, it is treated as without ID on writing, but as with ID on read and cleanup.
Consequences:
- The tag is never effectivelly invalidated by such ID
- The stale query references accumulate (instead of being cleaned up after unsubscribe)
Minimal reproduction
Tested on: "@reduxjs/toolkit": "2.12.0". Codesandbox project: https://codesandbox.io/p/devbox/t5myq5 (please look at the reproduction.js source file)
Use case
My use case was using a numeric ID starting with zero, which is a falsy value. The same behavior happens when using an empty string as the tag ID.
Using 0 or "" (empty string) as a tag ID is a legal use based on the Typescript definition:
export type FullTagDescription<TagType> = {
type: TagType
id?: number | string
}(packages/toolkit/src/query/endpointDefinitions.ts)
The cause
For a falsy tag ID, the following paths disagree:
- Write path:
id || '__internal_without_id'- falsy ID is regarded as tag without ID (packages/toolkit/src/query/core/buildSlice.ts) - Read path:
(tag.id !== undefined ? ... : ...)- falsy ID (which is not undefined) is regarded as a valid ID (packages/toolkit/src/query/core/buildSelectors.ts) - Cleanup path:
tag.id ?? '__internal_without_id'- falsy ID (which is not null/undefined) is regarded as a valid ID, so the original __internal_without_id bucket does not get cleaned up. (packages/toolkit/src/query/core/buildSlice.ts)
Suggested fix
For consistency, the same policy should be used in all places regarding falsy values. A minimal fix is to use tag.id ?? '__internal_without_id' in the write path.
Source: reduxjs/redux-toolkit