-
Notifications
You must be signed in to change notification settings - Fork 273
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* create a Jotai examples dir in cookbook with related utils, add state management recipes dir * add docs with examples * extract state from component to state, utils, simplify types and custom render func. * refactor: tweaks & cleanup --------- Co-authored-by: stevegalili <[email protected]> Co-authored-by: Maciej Jastrzębski <[email protected]>
- Loading branch information
1 parent
01d319c
commit d31c05a
Showing
10 changed files
with
2,507 additions
and
1,262 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import * as React from 'react'; | ||
import { render, screen, userEvent } from '@testing-library/react-native'; | ||
import { renderWithAtoms } from './test-utils'; | ||
import { TaskList } from './TaskList'; | ||
import { addTask, getAllTasks, newTaskTitleAtom, store, tasksAtom } from './state'; | ||
import { Task } from './types'; | ||
|
||
jest.useFakeTimers(); | ||
|
||
test('renders an empty task list', () => { | ||
render(<TaskList />); | ||
expect(screen.getByText(/no tasks, start by adding one/i)).toBeOnTheScreen(); | ||
}); | ||
|
||
const INITIAL_TASKS: Task[] = [{ id: '1', title: 'Buy bread' }]; | ||
|
||
test('renders a to do list with 1 items initially, and adds a new item', async () => { | ||
renderWithAtoms(<TaskList />, { | ||
initialValues: [ | ||
[tasksAtom, INITIAL_TASKS], | ||
[newTaskTitleAtom, ''], | ||
], | ||
}); | ||
|
||
expect(screen.getByText(/buy bread/i)).toBeOnTheScreen(); | ||
expect(screen.getAllByTestId('task-item')).toHaveLength(1); | ||
|
||
const user = userEvent.setup(); | ||
await user.type(screen.getByPlaceholderText(/new task/i), 'Buy almond milk'); | ||
await user.press(screen.getByRole('button', { name: /add task/i })); | ||
|
||
expect(screen.getByText(/buy almond milk/i)).toBeOnTheScreen(); | ||
expect(screen.getAllByTestId('task-item')).toHaveLength(2); | ||
}); | ||
|
||
test('modify store outside of components', () => { | ||
// Set the initial to do items in the store | ||
store.set(tasksAtom, INITIAL_TASKS); | ||
expect(getAllTasks()).toEqual(INITIAL_TASKS); | ||
|
||
const NEW_TASK = { id: '2', title: 'Buy almond milk' }; | ||
addTask(NEW_TASK); | ||
expect(getAllTasks()).toEqual([...INITIAL_TASKS, NEW_TASK]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import * as React from 'react'; | ||
import { Pressable, Text, TextInput, View } from 'react-native'; | ||
import { useAtom } from 'jotai'; | ||
import { nanoid } from 'nanoid'; | ||
import { newTaskTitleAtom, tasksAtom } from './state'; | ||
|
||
export function TaskList() { | ||
const [tasks, setTasks] = useAtom(tasksAtom); | ||
const [newTaskTitle, setNewTaskTitle] = useAtom(newTaskTitleAtom); | ||
|
||
const handleAddTask = () => { | ||
setTasks((tasks) => [ | ||
...tasks, | ||
{ | ||
id: nanoid(), | ||
title: newTaskTitle, | ||
}, | ||
]); | ||
setNewTaskTitle(''); | ||
}; | ||
|
||
return ( | ||
<View> | ||
{tasks.map((task) => ( | ||
<Text key={task.id} testID="task-item"> | ||
{task.title} | ||
</Text> | ||
))} | ||
|
||
{!tasks.length ? <Text>No tasks, start by adding one...</Text> : null} | ||
|
||
<TextInput | ||
accessibilityLabel="New Task" | ||
placeholder="New Task..." | ||
value={newTaskTitle} | ||
onChangeText={(text) => setNewTaskTitle(text)} | ||
/> | ||
|
||
<Pressable accessibilityRole="button" onPress={handleAddTask}> | ||
<Text>Add Task</Text> | ||
</Pressable> | ||
</View> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { atom, createStore } from 'jotai'; | ||
import { Task } from './types'; | ||
|
||
export const tasksAtom = atom<Task[]>([]); | ||
export const newTaskTitleAtom = atom(''); | ||
|
||
// Available for use outside React components | ||
export const store = createStore(); | ||
|
||
// Selectors | ||
export function getAllTasks(): Task[] { | ||
return store.get(tasksAtom); | ||
} | ||
|
||
// Actions | ||
export function addTask(task: Task) { | ||
store.set(tasksAtom, [...getAllTasks(), task]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import * as React from 'react'; | ||
import { render } from '@testing-library/react-native'; | ||
import { useHydrateAtoms } from 'jotai/utils'; | ||
import { PrimitiveAtom } from 'jotai/vanilla/atom'; | ||
|
||
// Jotai types are not well exported, so we will make our life easier by using `any`. | ||
export type AtomInitialValueTuple<T> = [PrimitiveAtom<T>, T]; | ||
|
||
export interface RenderWithAtomsOptions { | ||
initialValues: AtomInitialValueTuple<any>[]; | ||
} | ||
|
||
/** | ||
* Renders a React component with Jotai atoms for testing purposes. | ||
* | ||
* @param component - The React component to render. | ||
* @param options - The render options including the initial atom values. | ||
* @returns The render result from `@testing-library/react-native`. | ||
*/ | ||
export const renderWithAtoms = <T,>( | ||
component: React.ReactElement, | ||
options: RenderWithAtomsOptions, | ||
) => { | ||
return render( | ||
<HydrateAtomsWrapper initialValues={options.initialValues}>{component}</HydrateAtomsWrapper>, | ||
); | ||
}; | ||
|
||
export type HydrateAtomsWrapperProps = React.PropsWithChildren<{ | ||
initialValues: AtomInitialValueTuple<unknown>[]; | ||
}>; | ||
|
||
/** | ||
* A wrapper component that hydrates Jotai atoms with initial values. | ||
* | ||
* @param initialValues - The initial values for the Jotai atoms. | ||
* @param children - The child components to render. | ||
* @returns The rendered children. | ||
*/ | ||
function HydrateAtomsWrapper({ initialValues, children }: HydrateAtomsWrapperProps) { | ||
useHydrateAtoms(initialValues); | ||
return children; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export type Task = { | ||
id: string; | ||
title: string; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.