Skip to content

Commit 9e8e2aa

Browse files
author
stevegalili
committed
extract state from component to state, utils, simplify types and custom render func.
1 parent 1f5fcc1 commit 9e8e2aa

File tree

8 files changed

+11652
-5861
lines changed

8 files changed

+11652
-5861
lines changed

examples/cookbook/jotai/TodoList.test.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as React from 'react';
22
import { render, screen, userEvent } from '@testing-library/react-native';
3-
import { addTodo, getTodos, store, TodoItem, TodoList, todosAtom } from './TodoList';
43
import { renderWithAtoms } from './test-utils';
4+
import { TodoList } from './TodoList';
5+
import { addTodo, getTodos, store, todosAtom } from './state';
6+
import { TodoItem } from './types';
57

68
jest.useFakeTimers();
79
test('renders an empty to do list', () => {
@@ -19,14 +21,14 @@ test('renders a to do list with 1 items initially, and adds a new item', async (
1921
],
2022
});
2123
expect(screen.getByText(/buy bread/i)).toBeOnTheScreen();
22-
expect(screen.getAllByLabelText('todo-item')).toHaveLength(1);
24+
expect(screen.getAllByTestId('todo-item')).toHaveLength(1);
2325

2426
const user = userEvent.setup();
2527
const addTodoButton = screen.getByRole('button', { name: /add a random to-do/i });
2628
await user.press(addTodoButton);
2729

2830
expect(screen.getByText(/buy almond milk/i)).toBeOnTheScreen();
29-
expect(screen.getAllByLabelText('todo-item')).toHaveLength(2);
31+
expect(screen.getAllByTestId('todo-item')).toHaveLength(2);
3032
});
3133

3234
test("[outside react's scope]start with 1 initial todo and adds a new todo item", () => {

examples/cookbook/jotai/TodoList.tsx

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import * as React from 'react';
22
import { FlatList, Pressable, Text, View } from 'react-native';
3-
import { atom, createStore, useAtom } from 'jotai';
4-
5-
export type TodoItem = {
6-
id: string;
7-
text: string;
8-
};
9-
10-
export const todosAtom = atom<TodoItem[]>([]);
3+
import { useAtom } from 'jotai';
4+
import { generateRandomId } from './utils';
5+
import { todosAtom } from './state';
6+
import { TodoItem } from './types';
117

128
export function TodoList() {
139
const [todos, setTodos] = useAtom(todosAtom);
@@ -16,7 +12,7 @@ export function TodoList() {
1612
setTodos((prev) => [
1713
...prev,
1814
{
19-
id: Math.random().toString(36).slice(2, 11),
15+
id: generateRandomId(),
2016
text: 'Buy almond milk',
2117
},
2218
]);
@@ -30,7 +26,7 @@ export function TodoList() {
3026
<FlatList
3127
data={todos}
3228
renderItem={({ item }: { item: TodoItem }) => (
33-
<Text key={item.id} accessibilityLabel={'todo-item'}>
29+
<Text key={item.id} testID={'todo-item'}>
3430
{item.text}
3531
</Text>
3632
)}
@@ -41,11 +37,3 @@ export function TodoList() {
4137
</View>
4238
);
4339
}
44-
45-
// Available for use outside react components
46-
export const store = createStore();
47-
export const getTodos = (): TodoItem[] => store.get(todosAtom);
48-
export const addTodo = (newTodo: TodoItem) => {
49-
const todos = getTodos();
50-
store.set(todosAtom, [...todos, newTodo]);
51-
};

examples/cookbook/jotai/state.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { atom, createStore } from 'jotai';
2+
import { TodoItem } from './types';
3+
4+
export const todosAtom = atom<TodoItem[]>([]);
5+
6+
// Available for use outside react components
7+
export const store = createStore();
8+
export const getTodos = (): TodoItem[] => store.get(todosAtom);
9+
export const addTodo = (newTodo: TodoItem) => {
10+
const todos = getTodos();
11+
store.set(todosAtom, [...todos, newTodo]);
12+
};
Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import * as React from 'react';
2-
import {render} from '@testing-library/react-native';
3-
import {useHydrateAtoms} from "jotai/utils";
4-
import {IHydrateAtomsProps, InitialValues, IRenderWithAtomsOptions} from "./types";
2+
import { render } from '@testing-library/react-native';
3+
import { useHydrateAtoms } from "jotai/utils";
4+
import { HydrateAtomsWrapperProps, RenderWithAtomsOptions } from "./types";
55

6+
/**
7+
* A wrapper component that hydrates Jotai atoms with initial values.
8+
*
9+
* @template T - The type of the initial values for the atoms.
10+
* @param initialValues - The initial values for the Jotai atoms.
11+
* @param children - The child components to render.
12+
* @returns The rendered children.
13+
14+
*/
615
function HydrateAtomsWrapper<T>({
7-
initialValues,
8-
children,
9-
}: IHydrateAtomsProps<T>) {
10-
useHydrateAtoms(initialValues as unknown as InitialValues);
16+
initialValues,
17+
children,
18+
}: HydrateAtomsWrapperProps<T>) {
19+
useHydrateAtoms(initialValues);
1120
return children;
1221
}
1322

@@ -21,15 +30,13 @@ function HydrateAtomsWrapper<T>({
2130
*/
2231
export const renderWithAtoms = <T, >(
2332
component: React.ReactElement,
24-
options: IRenderWithAtomsOptions<T>,
33+
options: RenderWithAtomsOptions<T>,
2534
) => {
26-
const {initialValues} = options;
27-
return render(component, {
28-
wrapper: ({children}: { children: React.JSX.Element }) => (
29-
<HydrateAtomsWrapper initialValues={initialValues}>
30-
{children}
31-
</HydrateAtomsWrapper>
32-
),
33-
...options,
34-
});
35+
const {initialValues, ...rest} = options;
36+
37+
const ui = <HydrateAtomsWrapper initialValues={initialValues}>
38+
{component}
39+
</HydrateAtomsWrapper>;
40+
41+
return render(ui, {...rest});
3542
};

examples/cookbook/jotai/types.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
import * as React from 'react';
1+
import { PropsWithChildren } from 'react';
22

33
import { PrimitiveAtom } from 'jotai/vanilla/atom';
4-
import { useHydrateAtoms } from 'jotai/utils';
5-
import { RenderOptions as RntlRenderOptions } from '@testing-library/react-native';
4+
import { RenderOptions } from '@testing-library/react-native';
65

6+
// We define WithInitialValue, InitialValue and InitialValuesProps types
7+
// to help us define the initial values type props that we will pass to the
8+
// renderWithAtoms function and the HydrateAtomsWrapper component.
79
type WithInitialValue<Value> = {
810
init: Value;
911
};
10-
type UseHydrateAtomsParams = Parameters<typeof useHydrateAtoms>;
11-
export type InitialValues = UseHydrateAtomsParams[0];
1212
export type InitialValue<Value> = [PrimitiveAtom<Value> & WithInitialValue<Value>, Value];
1313

14-
export interface IRenderWithAtomsOptions<T> extends RntlRenderOptions {
14+
export interface InitialValuesProps<T> {
1515
initialValues: Array<InitialValue<T>>;
1616
}
1717

18-
export interface IHydrateAtomsProps<T> {
19-
initialValues: Array<InitialValue<T>>;
20-
children: React.JSX.Element;
21-
}
18+
export type HydrateAtomsWrapperProps<T> = PropsWithChildren<InitialValuesProps<T>>;
19+
20+
export interface RenderWithAtomsOptions<T> extends InitialValuesProps<T>, RenderOptions {}
21+
22+
export type TodoItem = {
23+
id: string;
24+
text: string;
25+
};

examples/cookbook/jotai/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const generateRandomId = () => Math.random().toString(36).slice(2, 11);

0 commit comments

Comments
 (0)