diff --git a/.changeset/lazy-locations-listen.md b/.changeset/lazy-locations-listen.md new file mode 100644 index 00000000000..d048bbebe4d --- /dev/null +++ b/.changeset/lazy-locations-listen.md @@ -0,0 +1,5 @@ +--- +'@tanstack/history': patch +--- + +Avoid generating unused history keys for locations with existing state. diff --git a/packages/history/src/index.ts b/packages/history/src/index.ts index b0e006c80da..6b7d642e5ed 100644 --- a/packages/history/src/index.ts +++ b/packages/history/src/index.ts @@ -697,8 +697,10 @@ export function parseHref( const sanitizedHref = normalizeHref(href) const hashIndex = sanitizedHref.indexOf('#') const searchIndex = sanitizedHref.indexOf('?') - - const addedKey = createRandomKey() + if (!state) { + const key = createRandomKey() + state = { [stateIndexKey]: 0, key, __TSR_key: key } + } return { href: sanitizedHref, @@ -720,7 +722,7 @@ export function parseHref( hashIndex === -1 ? undefined : hashIndex, ) : '', - state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey }, + state, } } diff --git a/packages/history/tests/parseHref.test.ts b/packages/history/tests/parseHref.test.ts index e1a95fafa45..10df49d330d 100644 --- a/packages/history/tests/parseHref.test.ts +++ b/packages/history/tests/parseHref.test.ts @@ -21,6 +21,30 @@ describe('parseHref', () => { expect(parsed.hash).toEqual('#qux') }) + test.each([ + { __TSR_index: 2 }, + { __TSR_index: 2, key: 'legacy-key', __TSR_key: 'current-key' }, + ])('preserves supplied state %j', (state) => { + Object.freeze(state) + expect(parseHref('/foo', state).state).toBe(state) + }) + + test('creates independent initial states with matching history keys', () => { + const first = parseHref('/foo', undefined) + const second = parseHref('/bar', undefined) + + for (const location of [first, second]) { + expect(location.state).toEqual({ + __TSR_index: 0, + key: expect.any(String), + __TSR_key: location.state.key, + }) + } + + first.state.__TSR_index = 1 + expect(second.state.__TSR_index).toBe(0) + }) + describe('open redirect prevention', () => { test('strips CR characters to prevent open redirect', () => { // If \r (CR) is in the href, it should be stripped