Formik's use with react-query onSubmit throwing "Invalid hook call" - formik

I have a React Native form that I'm trying to use Formik with react-query.
The problem is using useQuery() in a function called from onSubmit I am getting hook errors:
Warning: An unhandled error was caught from submitForm() [Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
I believe I understand what the error is but I don't understand how to do a workaround to get this working.
The example I threw together just to demonstrate the issue:
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { Button, StyleSheet, Text, TextInput, View } from 'react-native';
import { useQuery } from 'react-query';
import { Formik } from 'formik';
import axios from 'axios';
const getPokemonList = async () => {
const { data } = await axios.get("https://pokeapi.co/api/v2/pokemon");
return data;
};
function authenticate(username, password) {
const { isLoading, error, data } = useQuery('fetchLuke', getPokemonList);
if (data) {
{
return (
<Text>
{JSON.stringify(data, null, 2)}
</Text>
);
}
}
if (error) {
return (
<Text>{error}</Text>
);
}
if ( isLoading ) {
return (
<Text>Retrieving Luke Skywalker Information...</Text>
);
}
}
export default function App() {
return (
<View style={styles.container}>
<Formik
initialValues={{ email: '' }}
onSubmit={(values, actions) => {
authenticate(values.email);
actions.resetForm();
}}
>
{({
handleChange,
handleBlur,
handleSubmit, values }) => <View>
<TextInput
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
<Button onPress={handleSubmit} title="Submit" />
</View>}
</Formik>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

This is fairly straightforward.
Create a variable and assign useQuery to it (in App, because useQuery is a hook and hooks can only be used in functional components)
Call the variable in your onSubmit or pass it to authenticate...

react-query has a dependent query feature where the query only runs when a custom condition is satisfied.
Check the example below from https://react-query.tanstack.com/guides/dependent-queries
// Get the user
const { data: user } = useQuery(['user', email], getUserByEmail)
const userId = user?.id
// Then get the user's projects
const { isIdle, data: projects } = useQuery(
['projects', userId],
getProjectsByUser,
{
// The query will not execute until the userId exists
enabled: !!userId,
}
)
// isIdle will be `true` until `enabled` is true and the query begins to fetch.
// It will then go to the `isLoading` stage and hopefully the `isSuccess` stage :)

Related

Apollo GraphQL pass object

In GraphQL, how do I pass an object instead of a string?
Take this code from Apollo's website as an example, with my minor change:
import React, { useState } from 'react';
import { useLazyQuery } from '#apollo/client';
function DelayedQuery() {
const [dog, setDog] = useState(null);
const [getDog, { loading, data }] = useLazyQuery(GET_DOG_PHOTO);
if (loading) return <p>Loading ...</p>;
if (data && data.dog) {
setDog(data.dog);
}
const myObject = {
type: {
favors: [
tom: true,
bill: false
]
}
}
return (
<div>
{dog && <img src={dog.displayImage} />}
<button onClick={() => getDog({ variables: { theObject: myObject } })}>
Click me!
</button>
</div>
);
}
I believe React is trying to parse the object into a string, but (as the error message explains) JSON.stringify cannot serialize cyclic structures.
What do I do?

How to show Timeout for 25000 ms if API does not give data using redux?

I am new to react and redux. I have implemented API fetching using redux but not sure where should i put code for Timeout if API does not give gives data for particular time. everything is working fine I am getting data too..only thing i stuck is how to show timeout. Is there any way to do that? Thanks in advance :)
reducer.js
export const GET_REPOS = 'my-awesome-app/repos/LOAD';
export const GET_REPOS_SUCCESS = 'my-awesome-app/repos/LOAD_SUCCESS';
export const GET_REPOS_FAIL = 'my-awesome-app/repos/LOAD_FAIL';
const initialState = {
repos: [],
loading: false,
error: null
};
export default function reducer(state = initialState , action) {
switch (action.type) {
case GET_REPOS:
return { ...state, loading: true };
case GET_REPOS_SUCCESS:
return { ...state, loading: false, repos: action.payload.data };
case GET_REPOS_FAIL:
return {
...state,
loading: false,
error: 'Error while fetching repositories',
};
default:
return state;
}
}
export function listRepos(photos) {
return {
type: GET_REPOS,
payload: {
request: {
url: `photos/`
}
}
};
}
export function listThumb(albumId) {
return {
type: GET_REPOS,
payload: {
request: {
url: `photos?albumId=${albumId}`
}
}
};
}
home.js
import React, { Component } from 'react';
import { ActivityIndicator } from 'react-native-paper';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import styles from '../HomeComponent/style';
import { Ionicons } from '#expo/vector-icons';
import { listRepos } from '../../../reducer';
import ErrorAlert from '../../common/ErrorAlertComponent/errorAlert';
class Home extends Component {
componentDidMount() {
this.props.listRepos('');
}
FlatListItemSeparator = () => (
<View style={styles.flatListItemSeparator} />
)
renderItem = ({ item }) => (
<View style={styles.listRowContainer}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('ThumbnailViewScreen', {
albumID: item.id,
})} style={styles.listRow}>
<View style={styles.listTextNavVIew}>
<Text style={styles.albumTitle}> {item.title} </Text>
<Ionicons name='md-arrow-dropright' style={styles.detailArrow} />
</View>
</TouchableOpacity>
</View>
);
render() {
const { error, loading, products } = this.props;
if (error) {
return <ErrorAlert />;
}
if (loading) {
return (
<View style={{ flex: 1, paddingTop: 30 }}>
<ActivityIndicator animating={true} size='large' />
</View>
);
}
const { repos } = this.props;
return (
<View style={styles.MainContainer} >
<FlatList
styles={styles.container}
data={repos}
renderItem={this.renderItem}
ItemSeparatorComponent={this.FlatListItemSeparator}
/>
</View>
);
}
}
const mapStateToProps = state => {
let storedRepositories = state.repos.map(repo => ({ key: repo.id.toString(), ...repo }));
return {
repos: storedRepositories,
loading: state.loading,
error: state.error
};
};
const mapDispatchToProps = {
listRepos
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Firstly you should create entry point for your axios. For example:
import axios from 'axios/index';
const api = axios.create({
baseURL: 'site.com',
timeout: 25000,
});
export default api;
And import it where you do api calls:
import api from 'yourDirectory';
And use this entry point:
api.get(url)
If request time is too long axios throws timeout error after 25000ms

test Image onError call in react native

I have a component that wraps Image component and calls an internal function when Image's on error gets called
render() {
const {
avatars, userId, size, containerStyle, onPress, currentUserId,
} = this.props
const { hasError } = this.state
this.onError = this.onError.bind(this)
const id = userId || currentUserId
return (
<TouchableWithoutFeedback onPress={onPress}>
<Image
source={avatars && avatars[id] && !hasError
? { uri: avatars[id].localPath } : defaultAvatar}
style={{
width: size, height: size, borderRadius: size / 2, ...containerStyle,
}}
onError={this.onError}
/>
</TouchableWithoutFeedback>
)
}
}
Now I want to test my components this.onError function works correctly, is there a way to mock react-native image component and force it to run the onError function so I can test my fuction using jest?
In short, this is how you want to trigger onError function in your test
fireEvent(getByTestId("img"), "error");
You can of course choose to use any other getter instead of getByTestId if you want, but I prefer to have testId's in most cases.
Below I have explained it with a more detailed example.
If my component is this :
const DisplayAnImage = () => {
return (
<Image
testID="imgTest"
style={{width: 50, height: 50}}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/>
);
}
export default DisplayAnImage;
Then I would trigger onError function like this in my test :
test("test to trigger on error function", async () => {
const { getByTestId } = render(<DisplayAnImage />);
fireEvent(getByTestId("imgTest"), "error");
// write your assertions here
});
Just add a invalid uri in source prop.
e.g
Just replace
uri: avatars[id].localPath
with
uri: avatars[id].localPath + ".random"
I found the best solution was just to simulate the error callback like this
wrapper.find('Image').simulate('error')
expect(wrapper.state('hasError')).toEqual(true)

Trying to test a component using the useQuery hook but onCompleted causing issues

I am trying to test the following component, but cannot get it to render in Jest once data is fetched due to an error on the query's onCompleted prop. I have another component that is essentially the same, but does not utilise onCompleted at all and presents no trouble to Jest.
Here is the component in question, with some code reduced for brevity purposes:
import { trackProductListView } from "src/shared/components/analytics";
import searchResourcesQuery from "./search-resources.graphql";
const SearchResources = ({ filter, orderBy, query }) => {
const { loading, error, data, fetchMore } = useQuery(searchResourcesQuery, {
variables: {
orderBy,
filter,
query
},
onCompleted: ({ searchResources: { results } }) =>
results && trackProductListView("Search", null, results, 1)
});
...
return (
<div>
{!results.length ? (
<EmptySearch variant="resources" />
) : (
<InfiniteResources
trackContext="Search"
hasMore={!!searchAfter}
loadMoreEntries={loadMoreEntries}
resources={results}
/>
)}
</div>
);
};
And here is the test, although I have removed the mock results (again, for brevity purposes):
import React from "react";
import { act, wait } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import { MockedProvider } from "#apollo/react-testing";
import renderWithRouter from "src/shared/test-utils/renderWithRouter.js";
import SearchResources from "./search-resources";
import searchResourcesQuery from "./search-resources.graphql";
const mocks = [
{
request: {
query: searchResourcesQuery,
variables: { query: "test" }
},
result: {
data: {
searchResources: {
searchAfter: null,
results: [],
__typename: "ResourceList"
}
}
}
}
];
it("renders", async () => {
const { getByText } = renderWithRouter(
<MockedProvider addTypename={false} mocks={mocks}>
<SearchResources query="test" />
</MockedProvider>
);
await act(() => wait(() => getByText("Resource Partner Link test")));
expect(getByText("Resource Partner Link test")).toBeInTheDocument();
});
Running these tests results in:
TypeError: (0 , _analytics.trackProductListView) is not a function
Any help fixing this is most appreciated!
Well, turns out I could just mock the trackProductListView function

How to save and display and image in react-native?

I have a question in react-native. Im using a module called "react-native-image-picker" to pick an image and display it on my app.
Now what i want is to store it somewhere (database, or local storage) and when i open again the app, the image that i choosed should be there. But i dont know what is the best option to do it.
I've already tryied to read some stuff like react-native-fs and fetch-blob but it doesnt help me, i guess.
What is the best option to do it?
Thank you.
First, renders view according to condition. For example if image is available then simply display the image else display TouchableOpacity which will help use to select pictures :
import React, { Component } from React;
import { View, TouchableOpacity, Text, Image } from 'react-native';
import ImagePicker from 'react-native-image-picker';
import AsyncStorage from '#react-native-community/async-storage';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isImageAvailable: false,
profilePic: null
}
}
componentDidMount = () => {
this.getImage();
}
getImage = async () => {
const profilePic = await AsyncStorage.getItem("profilePic");
if (profilePic) {
this.setState({
isImageAvailable: true,
profilePic: JSON.parse(profilePic)
});
}
}
selectProfilePic = () => {
const options = {
title: 'Select Avatar',
storageOptions: {
skipBackup: true,
path: 'images',
},
};
ImagePicker.showImagePicker(options, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
const source = { uri: response.uri };
// You can also display the image using data:
// const source = { uri: 'data:image/jpeg;base64,' + response.data };
AsyncStorage.setItem("profilePic", JSON.stringify(source));
this.setState({
profilePic: source,
isImageAvailable: true
});
}
});
}
render() {
return (
<View>
{
this.state.isImageAvailable && (
<Image source={this.state.profilePic} style={{ width: 200, height: 200 }} />
)
}
{
!this.state.isImageAvailable && (
<TouchableOpacity onPress={this.selectProfilePic}>
<Text>Choose Profile Pic</Text>
</TouchableOpacity>
)
}
</View>
)
}
}
Hope it will help you.
You can use realmdb as an aternative to Asyncstorage.

Resources