Skip to content

Commit

Permalink
feat(atomWithLocation): override replace for specific navigations (#28
Browse files Browse the repository at this point in the history
)

* allow passing `replace` for specific navigations

* update according to PR comment

* test for new option

* update test for override

* remove unused import

* update type according to PR comment

* update test to correctly test override

* update type for override options
  • Loading branch information
Flirre authored Feb 18, 2024
1 parent 1cf77aa commit 73f8f4f
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 6 deletions.
82 changes: 81 additions & 1 deletion __tests__/atomWithLocation_spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAtom } from 'jotai';
import React, { StrictMode } from 'react';
import { render } from '@testing-library/react';
import { act, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { atomWithLocation } from '../src/index';

Expand Down Expand Up @@ -114,6 +114,86 @@ describe('atomWithLocation', () => {
expect(window.location.pathname).toEqual('/1');
expect(window.history.length).toEqual(3);
});

it('can override atomOptions', async () => {
const locationAtom = atomWithLocation({ replace: false });

const Navigation = () => {
const [location, setLocation] = useAtom(locationAtom);
return (
<>
<div> current pathname in atomWithLocation: {location.pathname} </div>
<button type="button" onClick={() => window.history.back()}>
back
</button>
<button
type="button"
onClick={() => setLocation({ pathname: '/123' })}
>
button1
</button>
<button
type="button"
onClick={() =>
setLocation(
{
pathname: '/234',
},
{ replace: true },
)
}
>
button2
</button>
</>
);
};

const { findByText, getByText } = render(
<StrictMode>
<Navigation />
</StrictMode>,
);

const previousTestHistoryLength = 3;

await act(async () => {
window.history.pushState(null, '', '/');
});

await findByText('current pathname in atomWithLocation: /');
expect(window.location.pathname).toEqual('/');
expect(window.history.length).toEqual(previousTestHistoryLength);

await userEvent.click(getByText('button1'));

await findByText('current pathname in atomWithLocation: /123');
expect(window.location.pathname).toEqual('/123');
expect(window.history.length).toEqual(previousTestHistoryLength + 1);

await userEvent.click(getByText('button2'));

await findByText('current pathname in atomWithLocation: /234');
expect(window.location.pathname).toEqual('/234');
expect(window.history.length).toEqual(previousTestHistoryLength + 1);

await userEvent.click(getByText('back'));

await findByText('current pathname in atomWithLocation: /');
expect(window.location.pathname).toEqual('/');
expect(window.history.length).toEqual(previousTestHistoryLength + 1);

// The first click overwrites the history entry we
// went back from. The history length remains the same.
await userEvent.click(getByText('button1'));

// The second click adds a new history entry, which now increments the history length.
await userEvent.click(getByText('button1'));

await findByText('current pathname in atomWithLocation: /123');
expect(window.location.pathname).toEqual('/123');
expect(window.history.length).toEqual(previousTestHistoryLength + 2);
});
});

describe('atomWithLocation without window', () => {
Expand Down
16 changes: 11 additions & 5 deletions src/atomWithLocation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { atom } from 'jotai/vanilla';
import type { PrimitiveAtom, SetStateAction } from 'jotai/vanilla';
import type { SetStateAction, WritableAtom } from 'jotai/vanilla';

type Location = {
pathname?: string;
Expand Down Expand Up @@ -50,13 +50,19 @@ type Options<T> = {
type RequiredOptions<T> = Omit<Options<T>, 'getLocation' | 'applyLocation'> &
Required<Pick<Options<T>, 'getLocation' | 'applyLocation'>>;

type AtomOptions<T> = Pick<Options<T>, 'replace'>;

export function atomWithLocation(
options?: Options<Location>,
): PrimitiveAtom<Location>;
): WritableAtom<
Location,
[SetStateAction<Location>, AtomOptions<Location>?],
void
>;

export function atomWithLocation<T>(
options: RequiredOptions<T>,
): PrimitiveAtom<T>;
): WritableAtom<T, [SetStateAction<T>, AtomOptions<T>?], void>;

export function atomWithLocation<T>(options?: Options<T>) {
const getL =
Expand All @@ -80,9 +86,9 @@ export function atomWithLocation<T>(options?: Options<T>) {
};
const derivedAtom = atom(
(get) => get(baseAtom),
(get, set, arg: SetStateAction<T>) => {
(get, set, arg: SetStateAction<T>, atomOptions: AtomOptions<T> = {}) => {
set(baseAtom, arg);
appL(get(baseAtom), options);
appL(get(baseAtom), { ...options, ...atomOptions });
},
);
return derivedAtom;
Expand Down

0 comments on commit 73f8f4f

Please sign in to comment.