diff --git a/README.md b/README.md index dbb13e2..e4a569e 100644 --- a/README.md +++ b/README.md @@ -109,14 +109,14 @@ const anAtom = atom(0); const Header = () => { const num = useAtomValueWithSchedule(anAtom, { - priority: LowPriority, + priority: LOW_PRIORITY, }); return
Header-{num}
; }; const Footer = () => { const num = useAtomValueWithSchedule(anAtom, { - priority: LowPriority, + priority: LOW_PRIORITY, }); return
Footer-{num}
; }; @@ -128,7 +128,7 @@ const Sidebar = () => { const Content = () => { const [num, setNum] = useAtomWithSchedule(anAtom, { - priority: ImmediatePriority, + priority: IMMEDIATE_PRIORITY, }); return (
diff --git a/__tests__/basic.test.tsx b/__tests__/basic.test.tsx new file mode 100644 index 0000000..4aef7a5 --- /dev/null +++ b/__tests__/basic.test.tsx @@ -0,0 +1,56 @@ +import { atom } from 'jotai' +import { render, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useEffect, useRef } from 'react' + +import { useAtomValueWithSchedule, useSetAtomWithSchedule } from '../src' + +const useCommitCount = () => { + const commitCountRef = useRef(1) + useEffect(() => { + commitCountRef.current += 1 + }) + return commitCountRef.current +} + +describe('the same behavior as jotai if the `priority` field is not passed', () => { + it('trigger state updates correctly', async () => { + const anAtom = atom(0) + function Control() { + const dispatch = useSetAtomWithSchedule(anAtom) + return ( + <> + +
Control commits: {useCommitCount()}
+ + ) + } + function Display() { + const num = useAtomValueWithSchedule(anAtom) + return ( + <> +
number: {num}
+
Display commits: {useCommitCount()}
+ + ) + } + function App() { + return ( + <> + + + + ) + } + const { findByText, getByText } = render() + await findByText('number: 0') + await userEvent.click(getByText('button')) + await waitFor(() => { + findByText('number: 1') + findByText('Display commits: 1') + findByText('Control commits: 2') + }) + }) +}) diff --git a/__tests__/priority.test.tsx b/__tests__/priority.test.tsx new file mode 100644 index 0000000..f625ec2 --- /dev/null +++ b/__tests__/priority.test.tsx @@ -0,0 +1,67 @@ +import { atom } from 'jotai' +import { render, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' + +import { + IMMEDIATE_PRIORITY, + LOW_PRIORITY, + NORMAL_PRIORITY, + useAtomValueWithSchedule, + useSetAtomWithSchedule, +} from '../src' + +it('batching rendering if we pass different `priority`', async () => { + const anAtom = atom(0) + function ImmediatePriorityCpn() { + const num = useAtomValueWithSchedule(anAtom, { + priority: IMMEDIATE_PRIORITY, + }) + return
number of ImmediatePriorityCpn: {num}
+ } + function NormalPriorityCpn() { + const num = useAtomValueWithSchedule(anAtom, { + priority: NORMAL_PRIORITY, + }) + return
number of NormalPriorityCpn: {num}
+ } + function LowPriorityCpn() { + const num = useAtomValueWithSchedule(anAtom, { + priority: LOW_PRIORITY, + }) + return
number of LowPriorityCpn: {num}
+ } + function App() { + const dispatch = useSetAtomWithSchedule(anAtom) + return ( + <> + + + + + + ) + } + const { findByText, getByText } = render() + + await userEvent.click(getByText('button')) + + await waitFor(() => { + findByText('number of ImmediatePriorityCpn: 1') + findByText('number of NormalPriorityCpn: 0') + findByText('number of LowPriorityCpn: 0') + }) + + await waitFor(() => { + findByText('number of ImmediatePriorityCpn: 1') + findByText('number of NormalPriorityCpn: 1') + findByText('number of LowPriorityCpn: 0') + }) + + await waitFor(() => { + findByText('number of ImmediatePriorityCpn: 1') + findByText('number of NormalPriorityCpn: 1') + findByText('number of LowPriorityCpn: 1') + }) +}) diff --git a/src/useSetAtomWithSchedule.ts b/src/useSetAtomWithSchedule.ts index 1d1f0d7..a82677a 100644 --- a/src/useSetAtomWithSchedule.ts +++ b/src/useSetAtomWithSchedule.ts @@ -22,9 +22,7 @@ export function useSetAtomWithSchedule( const store = useStore(options) const setAtom = useCallback( (...args: Args) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (import.meta.env?.MODE !== 'production' && !('write' in atom)) { + if (!('write' in atom)) { // useAtom can pass non writable atom with wrong type assertion, // so we should check here. throw new Error('not writable atom') diff --git a/src/workLoop.ts b/src/workLoop.ts index 9388976..202844c 100644 --- a/src/workLoop.ts +++ b/src/workLoop.ts @@ -47,12 +47,18 @@ function handleNextBatch() { } } -const channel = new MessageChannel() -const port = channel.port2 -channel.port1.onmessage = handleNextBatch - -export const enqueueWorkExecution = () => { - port.postMessage(null) +let enqueueWorkExecution: () => void +if (typeof MessageChannel !== 'undefined') { + const channel = new MessageChannel() + const port = channel.port2 + channel.port1.onmessage = handleNextBatch + enqueueWorkExecution = (): void => { + port.postMessage(null) + } +} else { + enqueueWorkExecution = (): void => { + setTimeout(handleNextBatch, 0) + } } export const initiateWorkLoop = () => { diff --git a/tsconfig.json b/tsconfig.json index dedebcc..956bc59 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,5 +17,6 @@ "jotai-scheduler": ["./src/index.ts"] } }, + "include": ["src", "__tests__"], "exclude": ["dist", "examples", "jotai"] }