Skip to content

Commit 41e861a

Browse files
authored
Merge pull request #22 from yf-yang/react-compiler
React compiler
2 parents e5f4c61 + 3d36d42 commit 41e861a

11 files changed

+690
-41
lines changed

.changeset/sweet-nails-punch.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'jotai-x': patch
3+
---
4+
5+
Add hooks `useStoreValue`, `useStoreSet`, `useStoreState`, `useStoreAtomValue`, `useStoreAtomSet`, `useStoreAtomState` to ease react-compiler eslint plugin complains

.eslintrc.cjs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = {
1313
'./config/eslint/bases/regexp.cjs',
1414
'./config/eslint/bases/jest.cjs',
1515
'./config/eslint/bases/react.cjs',
16+
'./config/eslint/bases/react-compiler.cjs',
1617
'./config/eslint/bases/rtl.cjs',
1718

1819
'./config/eslint/bases/unicorn.cjs',

README.md

+16-3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ The **`createAtomStore`** function returns an object (**`AtomStoreApi`**) contai
7070
- **`useValue`**: Hooks for accessing a state within a component, ensuring re-rendering when the state changes. See [useAtomValue](https://jotai.org/docs/core/use-atom#useatomvalue).
7171
``` js
7272
const store = useElementStore();
73+
74+
const element = useStoreValue('element');
75+
// alternative
7376
const element = store.useElementValue();
7477
// alternative
7578
const element = useElementStore().useValue('element');
@@ -78,27 +81,37 @@ The **`createAtomStore`** function returns an object (**`AtomStoreApi`**) contai
7881
``` js
7982
const store = useElementStore();
8083
81-
// Memoize the selector yourself
84+
// Approach 1: Memoize the selector yourself
8285
const toUpperCase = useCallback((element) => element.toUpperCase(), []);
8386
// Now it will only re-render if the uppercase value changes
87+
const element = useStoreValue(store, 'element', toUpperCase);
88+
// alternative
8489
const element = store.useElementValue(toUpperCase);
8590
// alternative
8691
const element = useElementStore().useValue('element', toUpperCase);
8792
88-
// Pass an dependency array to prevent re-renders
93+
// Approach 2: Pass an dependency array to prevent re-renders
8994
const [n, setN] = useState(0); // n may change during re-renders
90-
const numNthCharacter = useCallback((element) => element[n], [n]);
95+
const numNthCharacter = useStoreValue(store, 'element', (element) => element[n], [n]);
96+
// alternative
97+
const numNthCharacter = store.useElementValue((element) => element[n], [n]);
98+
// alternative
99+
const numNthCharacter = store.useValue('element', (element) => element[n], [n]);
91100
```
92101
- **`useSet`**: Hooks for setting a state within a component. See [useSetAtom](https://jotai.org/docs/core/use-atom#usesetatom).
93102
``` js
94103
const store = useElementStore();
104+
const element = useStoreSet(store, 'element');
105+
// alternative
95106
const element = store.useSetElement();
96107
// alternative
97108
const element = useElementStore().useSet('element');
98109
```
99110
- **`useState`**: Hooks for accessing and setting a state within a component, ensuring re-rendering when the state changes. See [useAtom](https://jotai.org/docs/core/use-atom).
100111
``` js
101112
const store = useElementStore();
113+
const element = useStoreState(store, 'element');
114+
// alternative
102115
const element = store.useElementState();
103116
// alternative
104117
const element = useElementStore().useState('element');
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
plugins: ['react-compiler'],
3+
rules: {
4+
'react-compiler/react-compiler': 'error',
5+
},
6+
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"eslint-plugin-prettier": "^5.0.1",
8484
"eslint-plugin-promise": "^6.1.1",
8585
"eslint-plugin-react": "^7.33.2",
86+
"eslint-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118",
8687
"eslint-plugin-react-hooks": "^4.6.0",
8788
"eslint-plugin-regexp": "^2.1.2",
8889
"eslint-plugin-testing-library": "^6.2.0",

packages/jotai-x/README.md

+16-3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ The **`createAtomStore`** function returns an object (**`AtomStoreApi`**) contai
7070
- **`useValue`**: Hooks for accessing a state within a component, ensuring re-rendering when the state changes. See [useAtomValue](https://jotai.org/docs/core/use-atom#useatomvalue).
7171
``` js
7272
const store = useElementStore();
73+
74+
const element = useStoreValue('element');
75+
// alternative
7376
const element = store.useElementValue();
7477
// alternative
7578
const element = useElementStore().useValue('element');
@@ -78,27 +81,37 @@ The **`createAtomStore`** function returns an object (**`AtomStoreApi`**) contai
7881
``` js
7982
const store = useElementStore();
8083
81-
// Memoize the selector yourself
84+
// Approach 1: Memoize the selector yourself
8285
const toUpperCase = useCallback((element) => element.toUpperCase(), []);
8386
// Now it will only re-render if the uppercase value changes
87+
const element = useStoreValue(store, 'element', toUpperCase);
88+
// alternative
8489
const element = store.useElementValue(toUpperCase);
8590
// alternative
8691
const element = useElementStore().useValue('element', toUpperCase);
8792
88-
// Pass an dependency array to prevent re-renders
93+
// Approach 2: Pass an dependency array to prevent re-renders
8994
const [n, setN] = useState(0); // n may change during re-renders
90-
const numNthCharacter = useCallback((element) => element[n], [n]);
95+
const numNthCharacter = useStoreValue(store, 'element', (element) => element[n], [n]);
96+
// alternative
97+
const numNthCharacter = store.useElementValue((element) => element[n], [n]);
98+
// alternative
99+
const numNthCharacter = store.useValue('element', (element) => element[n], [n]);
91100
```
92101
- **`useSet`**: Hooks for setting a state within a component. See [useSetAtom](https://jotai.org/docs/core/use-atom#usesetatom).
93102
``` js
94103
const store = useElementStore();
104+
const element = useStoreSet(store, 'element');
105+
// alternative
95106
const element = store.useSetElement();
96107
// alternative
97108
const element = useElementStore().useSet('element');
98109
```
99110
- **`useState`**: Hooks for accessing and setting a state within a component, ensuring re-rendering when the state changes. See [useAtom](https://jotai.org/docs/core/use-atom).
100111
``` js
101112
const store = useElementStore();
113+
const element = useStoreState(store, 'element');
114+
// alternative
102115
const element = store.useElementState();
103116
// alternative
104117
const element = useElementStore().useState('element');

packages/jotai-x/src/createAtomStore.spec.tsx

+118-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
/* eslint-disable react-compiler/react-compiler */
12
import '@testing-library/jest-dom';
23

34
import React, { useCallback } from 'react';
45
import { act, queryByText, render, renderHook } from '@testing-library/react';
56
import { atom, PrimitiveAtom, useAtomValue } from 'jotai';
67
import { splitAtom } from 'jotai/utils';
78

8-
import { createAtomStore } from './createAtomStore';
9+
import {
10+
createAtomStore,
11+
useStoreAtomValue,
12+
useStoreSet,
13+
useStoreState,
14+
useStoreValue,
15+
} from './createAtomStore';
916

1017
describe('createAtomStore', () => {
1118
describe('no unnecessary rerender', () => {
@@ -403,6 +410,30 @@ describe('createAtomStore', () => {
403410
);
404411
};
405412

413+
const BecomeFriendUseValueNoReactCompilerComplain = () => {
414+
// Just guarantee that the react compiler doesn't complain
415+
/* eslint-enable react-compiler/react-compiler */
416+
const store = useMyTestStoreStore();
417+
const becomeFriends1 = useStoreValue(store, 'becomeFriends');
418+
const becomeFriends2 = useStoreAtomValue(
419+
store,
420+
myTestStoreStore.atom.becomeFriends
421+
);
422+
423+
return (
424+
<button
425+
type="button"
426+
onClick={() => {
427+
becomeFriends1();
428+
becomeFriends2();
429+
}}
430+
>
431+
Become Friends
432+
</button>
433+
);
434+
/* eslint-disable react-compiler/react-compiler */
435+
};
436+
406437
const BecomeFriendsGet = () => {
407438
// Make sure both of these are actual functions, not wrapped functions
408439
const store = useMyTestStoreStore();
@@ -472,6 +503,28 @@ describe('createAtomStore', () => {
472503
);
473504
};
474505

506+
const BecomeFriendsUseSetNoReactCompilerComplain = () => {
507+
// Just guarantee that the react compiler doesn't complain
508+
/* eslint-enable react-compiler/react-compiler */
509+
const store = useMyTestStoreStore();
510+
const setBecomeFriends = useStoreSet(store, 'becomeFriends');
511+
const [becameFriends, setBecameFriends] = React.useState(false);
512+
513+
return (
514+
<>
515+
<button
516+
type="button"
517+
onClick={() => setBecomeFriends(() => setBecameFriends(true))}
518+
>
519+
Change Callback
520+
</button>
521+
522+
<div>useSetBecameFriends: {becameFriends.toString()}</div>
523+
</>
524+
);
525+
/* eslint-disable react-compiler/react-compiler */
526+
};
527+
475528
const BecomeFriendsSet = () => {
476529
const store = useMyTestStoreStore();
477530
const [becameFriends, setBecameFriends] = React.useState(false);
@@ -546,9 +599,31 @@ describe('createAtomStore', () => {
546599
);
547600
};
548601

602+
const BecomeFriendsUseStateNoReactCompilerComplain = () => {
603+
// Just guarantee that the react compiler doesn't complain
604+
/* eslint-enable react-compiler/react-compiler */
605+
const store = useMyTestStoreStore();
606+
const [, setBecomeFriends] = useStoreState(store, 'becomeFriends');
607+
const [becameFriends, setBecameFriends] = React.useState(false);
608+
609+
return (
610+
<>
611+
<button
612+
type="button"
613+
onClick={() => setBecomeFriends(() => setBecameFriends(true))}
614+
>
615+
Change Callback
616+
</button>
617+
618+
<div>useBecameFriends: {becameFriends.toString()}</div>
619+
</>
620+
);
621+
/* eslint-disable react-compiler/react-compiler */
622+
};
623+
549624
beforeEach(() => {
550-
renderHook(() => useMyTestStoreStore().useSetName()(INITIAL_NAME));
551-
renderHook(() => useMyTestStoreStore().useSetAge()(INITIAL_AGE));
625+
renderHook(() => useMyTestStoreStore().setName(INITIAL_NAME));
626+
renderHook(() => useMyTestStoreStore().setAge(INITIAL_AGE));
552627
});
553628

554629
it('passes default values from provider to consumer', () => {
@@ -797,6 +872,18 @@ describe('createAtomStore', () => {
797872
expect(getByText('becameFriends: true')).toBeInTheDocument();
798873
});
799874

875+
it('provides and useValue of functions with no react compiler complain', () => {
876+
const { getByText } = render(
877+
<BecomeFriendsProvider>
878+
<BecomeFriendUseValueNoReactCompilerComplain />
879+
</BecomeFriendsProvider>
880+
);
881+
882+
expect(getByText('becameFriends: false')).toBeInTheDocument();
883+
act(() => getByText('Become Friends').click());
884+
expect(getByText('becameFriends: true')).toBeInTheDocument();
885+
});
886+
800887
it('provides and get functions', () => {
801888
const { getByText } = render(
802889
<BecomeFriendsProvider>
@@ -849,6 +936,20 @@ describe('createAtomStore', () => {
849936
expect(getByText('useSetBecameFriends: true')).toBeInTheDocument();
850937
});
851938

939+
it('useSet of functions with no react compiler complain', () => {
940+
const { getByText } = render(
941+
<BecomeFriendsProvider>
942+
<BecomeFriendsUseSetNoReactCompilerComplain />
943+
<BecomeFriendsUseValueWithKeyParam />
944+
</BecomeFriendsProvider>
945+
);
946+
947+
act(() => getByText('Change Callback').click());
948+
expect(getByText('useSetBecameFriends: false')).toBeInTheDocument();
949+
act(() => getByText('Become Friends').click());
950+
expect(getByText('useSetBecameFriends: true')).toBeInTheDocument();
951+
});
952+
852953
it('set of functions', () => {
853954
const { getByText } = render(
854955
<BecomeFriendsProvider>
@@ -904,6 +1005,20 @@ describe('createAtomStore', () => {
9041005
act(() => getByText('Become Friends').click());
9051006
expect(getByText('useBecameFriends: true')).toBeInTheDocument();
9061007
});
1008+
1009+
it('use state functions with no react compiler complain', () => {
1010+
const { getByText } = render(
1011+
<BecomeFriendsProvider>
1012+
<BecomeFriendsUseStateNoReactCompilerComplain />
1013+
<BecomeFriendsUseValueWithKeyParam />
1014+
</BecomeFriendsProvider>
1015+
);
1016+
1017+
act(() => getByText('Change Callback').click());
1018+
expect(getByText('useBecameFriends: false')).toBeInTheDocument();
1019+
act(() => getByText('Become Friends').click());
1020+
expect(getByText('useBecameFriends: true')).toBeInTheDocument();
1021+
});
9071022
});
9081023

9091024
describe('scoped providers', () => {

0 commit comments

Comments
 (0)