-
Notifications
You must be signed in to change notification settings - Fork 278
docs(cookbook): network requests recipes #1651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
cbb1c24
example code
71a6afb
Merge branch 'main' into feat/cookbook/network-reqs
vanGalilea 89df27e
add users list with fetch example and corres. minimal test
vanGalilea 6c57ad7
Merge branch 'main' into feat/cookbook/network-reqs
vanGalilea 49e755d
rename to PhoneBook and add FavoritesList
vanGalilea fd2cba8
Test harness and proper mocking
vanGalilea cfdbe42
Compose Axios recipe
vanGalilea d105659
Compose Fetch recipe
vanGalilea 652d6de
Fix tune TS errors
fb1f45b
Fix info label
d52e36e
Sanity check failing test in CI
a37017d
Sanity check failing test in CI 2 - add waitForElementToBeRemoved
70791e5
Sanity check failing test in CI 3 - remove waitForElementToBeRemoved …
11e9407
Sanity check failing test in CI 4 - add console.logs
bf6b1f4
Debug tests failing in CI
vanGalilea 20f0aa6
Debug tests failing in CI 2
vanGalilea 1eef224
Debug tests failing in CI 3
vanGalilea 4608c88
jest.setTimeout to 7 s
vanGalilea e2aab55
isKnownError
vanGalilea 852da82
Increase to 10s
vanGalilea f7c35cc
isErrorWithMessage addition
vanGalilea d348f72
isErrorWithMessage addition
vanGalilea 29eff89
jest setTimeout 8s
vanGalilea 08a1e5c
useFakeTimers
vanGalilea dfaff9e
run with --detectOpenHandles
vanGalilea 1ca49e0
reset GH action
vanGalilea File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,13 @@ | ||
const chuckNorrisError = () => { | ||
throw Error( | ||
"Please ensure you mock 'Axios' - Only Chuck Norris is allowed to make API requests when testing ;)", | ||
); | ||
}; | ||
|
||
export default { | ||
get: jest.fn(chuckNorrisError), | ||
post: jest.fn(chuckNorrisError), | ||
put: jest.fn(chuckNorrisError), | ||
delete: jest.fn(chuckNorrisError), | ||
request: jest.fn(chuckNorrisError), | ||
}; |
This file contains hidden or 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
This file contains hidden or 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,52 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import { Text } from 'react-native'; | ||
import { User } from './types'; | ||
import ContactsList from './components/ContactsList'; | ||
import FavoritesList from './components/FavoritesList'; | ||
import getAllContacts from './api/getAllContacts'; | ||
import getAllFavorites from './api/getAllFavorites'; | ||
|
||
export default () => { | ||
const [usersData, setUsersData] = useState<User[]>([]); | ||
const [favoritesData, setFavoritesData] = useState<User[]>([]); | ||
const [error, setError] = useState<string | null>(null); | ||
|
||
useEffect(() => { | ||
const _getAllContacts = async () => { | ||
const _data = await getAllContacts(); | ||
setUsersData(_data); | ||
}; | ||
const _getAllFavorites = async () => { | ||
const _data = await getAllFavorites(); | ||
setFavoritesData(_data); | ||
}; | ||
|
||
const run = async () => { | ||
try { | ||
await Promise.all([_getAllContacts(), _getAllFavorites()]); | ||
} catch (e) { | ||
const message = isErrorWithMessage(e) ? e.message : 'Something went wrong'; | ||
setError(message); | ||
} | ||
}; | ||
|
||
void run(); | ||
}, []); | ||
|
||
if (error) { | ||
return <Text>An error occurred: {error}</Text>; | ||
} | ||
|
||
return ( | ||
<> | ||
<FavoritesList users={favoritesData} /> | ||
<ContactsList users={usersData} /> | ||
</> | ||
); | ||
}; | ||
|
||
const isErrorWithMessage = ( | ||
e: unknown, | ||
): e is { | ||
message: string; | ||
} => typeof e === 'object' && e !== null && 'message' in e; |
119 changes: 119 additions & 0 deletions
119
examples/cookbook/app/network-requests/__tests__/PhoneBook.test.tsx
This file contains hidden or 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,119 @@ | ||
import { render, screen, waitForElementToBeRemoved } from '@testing-library/react-native'; | ||
import React from 'react'; | ||
import axios from 'axios'; | ||
import PhoneBook from '../PhoneBook'; | ||
import { User } from '../types'; | ||
|
||
jest.mock('axios'); | ||
|
||
jest.setTimeout(10000); | ||
describe('PhoneBook', () => { | ||
it('fetches contacts successfully and renders in list', async () => { | ||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: true, | ||
json: jest.fn().mockResolvedValueOnce(DATA), | ||
}); | ||
(axios.get as jest.Mock).mockResolvedValue({ data: DATA }); | ||
render(<PhoneBook />); | ||
|
||
await waitForElementToBeRemoved(() => screen.getByText(/users data not quite there yet/i)); | ||
expect(await screen.findByText('Name: Mrs Ida Kristensen')).toBeOnTheScreen(); | ||
expect(await screen.findByText('Email: [email protected]')).toBeOnTheScreen(); | ||
expect(await screen.findAllByText(/name/i)).toHaveLength(3); | ||
}); | ||
|
||
it('fails to fetch contacts and renders error message', async () => { | ||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: false, | ||
}); | ||
(axios.get as jest.Mock).mockResolvedValue({ data: DATA }); | ||
render(<PhoneBook />); | ||
|
||
await waitForElementToBeRemoved(() => screen.getByText(/users data not quite there yet/i)); | ||
expect(await screen.findByText(/error fetching contacts/i)).toBeOnTheScreen(); | ||
}); | ||
|
||
it('fetches favorites successfully and renders all users avatars', async () => { | ||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: true, | ||
json: jest.fn().mockResolvedValueOnce(DATA), | ||
}); | ||
(axios.get as jest.Mock).mockResolvedValue({ data: DATA }); | ||
render(<PhoneBook />); | ||
|
||
await waitForElementToBeRemoved(() => screen.getByText(/figuring out your favorites/i)); | ||
expect(await screen.findByText(/my favorites/i)).toBeOnTheScreen(); | ||
expect(await screen.findAllByLabelText('favorite-contact-avatar')).toHaveLength(3); | ||
}); | ||
|
||
it('fails to fetch favorites and renders error message', async () => { | ||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: true, | ||
json: jest.fn().mockResolvedValueOnce(DATA), | ||
}); | ||
(axios.get as jest.Mock).mockRejectedValueOnce({ message: 'Error fetching favorites' }); | ||
render(<PhoneBook />); | ||
|
||
await waitForElementToBeRemoved(() => screen.getByText(/figuring out your favorites/i)); | ||
expect(await screen.findByText(/error fetching favorites/i)).toBeOnTheScreen(); | ||
}); | ||
}); | ||
|
||
const DATA: { results: User[] } = { | ||
results: [ | ||
{ | ||
name: { | ||
title: 'Mrs', | ||
first: 'Ida', | ||
last: 'Kristensen', | ||
}, | ||
email: '[email protected]', | ||
id: { | ||
name: 'CPR', | ||
value: '250562-5730', | ||
}, | ||
picture: { | ||
large: 'https://randomuser.me/api/portraits/women/26.jpg', | ||
medium: 'https://randomuser.me/api/portraits/med/women/26.jpg', | ||
thumbnail: 'https://randomuser.me/api/portraits/thumb/women/26.jpg', | ||
}, | ||
cell: '123-4567-890', | ||
}, | ||
{ | ||
name: { | ||
title: 'Mr', | ||
first: 'Elijah', | ||
last: 'Ellis', | ||
}, | ||
email: '[email protected]', | ||
id: { | ||
name: 'TFN', | ||
value: '138117486', | ||
}, | ||
picture: { | ||
large: 'https://randomuser.me/api/portraits/men/53.jpg', | ||
medium: 'https://randomuser.me/api/portraits/med/men/53.jpg', | ||
thumbnail: 'https://randomuser.me/api/portraits/thumb/men/53.jpg', | ||
}, | ||
cell: '123-4567-890', | ||
}, | ||
{ | ||
name: { | ||
title: 'Mr', | ||
first: 'Miro', | ||
last: 'Halko', | ||
}, | ||
email: '[email protected]', | ||
id: { | ||
name: 'HETU', | ||
value: 'NaNNA945undefined', | ||
}, | ||
picture: { | ||
large: 'https://randomuser.me/api/portraits/men/17.jpg', | ||
medium: 'https://randomuser.me/api/portraits/med/men/17.jpg', | ||
thumbnail: 'https://randomuser.me/api/portraits/thumb/men/17.jpg', | ||
}, | ||
cell: '123-4567-890', | ||
}, | ||
], | ||
}; |
10 changes: 10 additions & 0 deletions
10
examples/cookbook/app/network-requests/api/getAllContacts.ts
This file contains hidden or 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,10 @@ | ||
import { User } from '../types'; | ||
|
||
export default async (): Promise<User[]> => { | ||
const res = await fetch('https://randomuser.me/api/?results=25'); | ||
if (!res.ok) { | ||
throw new Error(`Error fetching contacts`); | ||
} | ||
const json = await res.json(); | ||
return json.results; | ||
}; |
7 changes: 7 additions & 0 deletions
7
examples/cookbook/app/network-requests/api/getAllFavorites.ts
This file contains hidden or 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,7 @@ | ||
import axios from 'axios'; | ||
import { User } from '../types'; | ||
|
||
export default async (): Promise<User[]> => { | ||
const res = await axios.get('https://randomuser.me/api/?results=10'); | ||
return res.data.results; | ||
}; |
60 changes: 60 additions & 0 deletions
60
examples/cookbook/app/network-requests/components/ContactsList.tsx
This file contains hidden or 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,60 @@ | ||
import { FlatList, Image, StyleSheet, Text, View } from 'react-native'; | ||
import React, { useCallback } from 'react'; | ||
import type { ListRenderItem } from '@react-native/virtualized-lists'; | ||
import { User } from '../types'; | ||
|
||
export default ({ users }: { users: User[] }) => { | ||
const renderItem: ListRenderItem<User> = useCallback( | ||
({ item: { name, email, picture, cell }, index }) => { | ||
const { title, first, last } = name; | ||
const backgroundColor = index % 2 === 0 ? '#f9f9f9' : '#fff'; | ||
return ( | ||
<View style={[{ backgroundColor }, styles.userContainer]}> | ||
<Image source={{ uri: picture.thumbnail }} style={styles.userImage} /> | ||
<View> | ||
<Text> | ||
Name: {title} {first} {last} | ||
</Text> | ||
<Text>Email: {email}</Text> | ||
<Text>Mobile: {cell}</Text> | ||
</View> | ||
</View> | ||
); | ||
}, | ||
[], | ||
); | ||
|
||
if (users.length === 0) return <FullScreenLoader />; | ||
|
||
return ( | ||
<View> | ||
<FlatList<User> | ||
data={users} | ||
renderItem={renderItem} | ||
keyExtractor={(item, index) => `${index}-${item.id.value}`} | ||
/> | ||
</View> | ||
); | ||
}; | ||
const FullScreenLoader = () => { | ||
return ( | ||
<View style={styles.loaderContainer}> | ||
<Text>Users data not quite there yet...</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
userContainer: { | ||
padding: 16, | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
}, | ||
userImage: { | ||
width: 50, | ||
height: 50, | ||
borderRadius: 24, | ||
marginRight: 16, | ||
}, | ||
loaderContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, | ||
}); |
59 changes: 59 additions & 0 deletions
59
examples/cookbook/app/network-requests/components/FavoritesList.tsx
This file contains hidden or 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,59 @@ | ||
import { FlatList, Image, StyleSheet, Text, View } from 'react-native'; | ||
import React, { useCallback } from 'react'; | ||
import type { ListRenderItem } from '@react-native/virtualized-lists'; | ||
import { User } from '../types'; | ||
|
||
export default ({ users }: { users: User[] }) => { | ||
const renderItem: ListRenderItem<User> = useCallback(({ item: { picture } }) => { | ||
return ( | ||
<View style={styles.userContainer}> | ||
<Image | ||
source={{ uri: picture.thumbnail }} | ||
style={styles.userImage} | ||
accessibilityLabel={'favorite-contact-avatar'} | ||
/> | ||
</View> | ||
); | ||
}, []); | ||
|
||
if (users.length === 0) return <FullScreenLoader />; | ||
|
||
return ( | ||
<View style={styles.outerContainer}> | ||
<Text>⭐My Favorites</Text> | ||
<FlatList<User> | ||
horizontal | ||
showsHorizontalScrollIndicator={false} | ||
data={users} | ||
renderItem={renderItem} | ||
keyExtractor={(item, index) => `${index}-${item.id.value}`} | ||
/> | ||
</View> | ||
); | ||
}; | ||
const FullScreenLoader = () => { | ||
return ( | ||
<View style={styles.loaderContainer}> | ||
<Text>Figuring out your favorites...</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
outerContainer: { | ||
padding: 8, | ||
}, | ||
userContainer: { | ||
padding: 8, | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
}, | ||
userImage: { | ||
width: 52, | ||
height: 52, | ||
borderRadius: 36, | ||
borderColor: '#9b6dff', | ||
borderWidth: 2, | ||
}, | ||
loaderContainer: { height: 52, justifyContent: 'center', alignItems: 'center' }, | ||
}); |
This file contains hidden or 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,6 @@ | ||
import * as React from 'react'; | ||
import PhoneBook from './PhoneBook'; | ||
|
||
export default function Example() { | ||
return <PhoneBook />; | ||
} |
This file contains hidden or 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 @@ | ||
export type User = { | ||
name: { | ||
title: string; | ||
first: string; | ||
last: string; | ||
}; | ||
email: string; | ||
id: { | ||
name: string; | ||
value: string; | ||
}; | ||
picture: { | ||
large: string; | ||
medium: string; | ||
thumbnail: string; | ||
}; | ||
cell: string; | ||
}; |
This file contains hidden or 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
This file contains hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this test suit runs in CI, the default 5s is not sufficient.
Locally it's a matter of 0.91 s.
Any idea what might be causing this on CI? @mdjastrzebski?