I have to integrate Stripe with my React Native Application this phone application is a phone version of a desktop application where stripe is already being integrated and works fine so the backend is already been implemented. I wanted to use the context i used i nthe react js application however the useElementwas not available in stripe/stripe-react-native however after some reasearch i figured it didn't matter if i used the #stripe/stripe-js library my problem now is i keep getting the error: Could not find Elements context; You need to wrap the part of your app that calls useElements() in an <Elements> provider. However i already wrapped the form in an provider this is my checkoutForm.js:
import { useNavigation } from '#react-navigation/native';
import
React,
{ useState,
useEffect, useRef }
from 'react'
import {
View,
Text,
SafeAreaView,
StatusBar,
StyleSheet,
TouchableOpacity,
ScrollView,
Image,
Pressable ,
TextInput,
Alert}
from 'react-native';
import Icon from '../components'
import { COLORS } from '../constants';
import { useStateContext } from '../context/StateContext';
import { ProductCarousel } from '../../components';
import { useElements, Elements} from "#stripe/react-stripe-js"
import { CardField, useConfirmPayment, useStripe, } from '#stripe/stripe-react-native';
import { Button } from 'react-native-elements';
const CheckoutForm = () => {
const navigation = useNavigation();
const [isDeliveryAddressOpen, setIsDeliveryAddressOpen] = useState(false);
const [isContactNumberOpen, setIsContactNumberOpen] = useState(false);
const [isDeliveryInstructionsOpen, setIsDeliveryInstructionsOpen] = useState(false);
const [isCartItemsOpen, setIsCartItemsOpen] = useState(false);
const [isPaymentInfoOpen, setIsPaymentInfoOpen] = useState(false);
//const { confirmPayment, loading } = useConfirmPayment();
const [success, setSuccess ] = useState(false)
const stripe = useStripe()
const elements = useElements()
const cardElement = useRef(null);
const { totalPrice, cartItems } = useStateContext();
const [fullName, setFullName] = useState('');
const [email, setEmail] = useState('');
const [address, setAddress] = useState('');
const [address2, setAddress2] = useState('');
const [state, setState] = useState('');
const [city, setCity] = useState('');
const [zipCode, setZipCode] = useState('');
const [primaryNumber, setPrimaryNumber] = useState('');
const [SecondaryNumber, setSecondaryNumber] = useState('');
const [DeliveryInstructions, setDeliveryInstructions] = useState('');
const [isProcessing, setIsProcessing] = useState(false);
const [isError, setIsError] = useState(false);
//const [cardDetails, setCardDetails] = useState('')
const toggleDeliveryAddress = () => {
setIsDeliveryAddressOpen(!isDeliveryAddressOpen);
};
const toggleContactNumber = () => {
setIsContactNumberOpen(!isContactNumberOpen);
};
const toggleDeliveryInstructions = () => {
setIsDeliveryInstructionsOpen(!isDeliveryInstructionsOpen);
};
const toggleCartItems = () => {
setIsCartItemsOpen(!isCartItemsOpen);
};
const togglePaymentInfo = () => {
setIsPaymentInfoOpen(!isPaymentInfoOpen);
};
//const navigate = useNavigate();
const carts = {
status: 'pending',
items: (cartItems),
address,
fullName,
zipCode,
state,
city,
DeliveryInstructions,
primaryNumber,
SecondaryNumber,
totalPrice
}
const handleCheckout = async () => {
const response = await fetch('/create_cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(carts),
});
if(response.statusCode === 500) return;
// eslint-disable-next-line
const data = await response.json();
console.log('presseddddd', data)
}
/*========================================================*/
const handleSubmit = async () => {
const {error, paymentMethod} = await stripe.createPaymentMethod({
type: "card",
card: elements.getElement(CardField),
billing_details: {
name: fullName,
phone: primaryNumber,
email: email,
address: {
city: city,
line1: address,
state: state,
postal_code: zipCode
}
},
})
if(!error) {
try {
const {id} = paymentMethod
const carts = {
id,
amount: totalPrice,
confirm: true,
currency: 'CAD'
}
setIsProcessing(true);
const response = await fetch('/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(carts),
});
if(response.status === 200) {
console.log(response.status)
console.log("Successful payment")
setSuccess(true)
}
const data = await response.json();
if(response.status >= 400) {
console.log(response.status)
console.log(data.error.split(':').pop())
setIsError(data.error)
}
} catch (error) {
console.log("Error", error)
}
} else {
console.log(error.message)
}
setIsProcessing(false)
//navigate('/');
}
return (
<View style={styles.container}>
<View style={styles.insideContainer}>
<TouchableOpacity style={styles.titleContainer} onPress={togglePaymentInfo}>
<Text style={styles.title}>Payment Info</Text>
{!isPaymentInfoOpen ?
<Icon icon ='add-outline' color='#000' size={20}/>
: <Icon icon ='remove-outline' color='#000' size={20}/>
}
</TouchableOpacity>
{isPaymentInfoOpen && (
<View style={styles.containercollapsed}>
<View style={{width: '98%'}}>
<CardField
ref={cardElement}
postalCodeEnabled={false}
placeholders={{
number: '4242 4242 4242 4242',
}}
cardStyle={{
backgroundColor: '#FFFFFF',
textColor: '#000000',
borderColor: COLORS.lightGray2,
borderWidth: 1,
borderRadius: 4
}}
style={{
width: '100%',
height: 50,
}}
onCardChange={(cardDetails) => {
}}
onFocus={(focusedField) => {
}}
/>
</View>
</View>
)}
</View>
<View style={styles.PaybuttonView}>
<Button
title="Pay"
onPress={ () => handleSubmit()}
/*disabled={loading}
loading={loading}*/
/>
{/*<Pressable style={styles.Paybutton} onPress={() => {handleCheckout(); handlePayPress()} }>
<Text style={{textAlign: 'center', fontSize: 20, color: '#FFFF', textTransform: 'uppercase'}}>Checkout</Text>
</Pressable>*/}
</View>
</View>
)
}
export default CheckoutForm
this is my StripeForm.js :
import React from 'react'
import {Elements} from "#stripe/react-stripe-js"
import CheckoutForm from './CheckoutForm';
import { loadStripe } from "#stripe/stripe-js"
const stripePromise = loadStripe(i removed the key but this is the key's place);
export const StipeForm = () => {
return (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
)
}
To initialize Stripe to be used with the Stripe React Native SDK you need to use either StripeProvider or initStripe. See the docs here. You can't use the React Stripe.JS library initialize Stripe for the React Native SDK.
Related
I have this page (screen) that receives via Params an ID number, in this Screen, I try to call an Action Function from my Action (reducer) file and gets an API call, I thought I didn't get any information in the Array from that call, I believe that the issue was in the Call, but I put a Console log after the declaration on the Action Function, but it didn't print so I think it didn't access to that function, so I believe the issue is in the Call of that function via Dispatch.
I even tried to put a Breakpoint inside the UseEfect where I call the Function that calls the Dispatch Function but it never breaks I'm not sure where is the error, this is the Code:
Screen (where I suspect the issue is):
```import React, {useState, useCallback, useEffect} from 'react';
import { ScrollView, Text, Image, StyleSheet, View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
const ProductDetailScreen = props => {
const playerId = props.route.params.id;
const estadId = props.route.params.statId;
const selectedPlayer = useSelector(state => state.jugadores.availablePlayers.find(prod => prod.id === playerId));
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const goles = useSelector(state => state.jugadores.playerGoals);
const dispatch = useDispatch();
const loadEstad = useCallback (async (param) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(userActions.fetchEstadistica(param));
} catch (err){
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
setIsLoading(true);
loadEstad(estadId).then(() => {
setIsLoading(false);
});
}, [dispatch, loadEstad]);
console.log(estadId);
console.log(goles);
return (
<ScrollView>
<Image style={styles.image} source={{ uri: selectedPlayer.imagen }} />
<View style={styles.dataContainer}>
<Text style={styles.description}>Numero: <Text style={styles.subtitle}>{selectedPlayer.numero}</Text></Text>
<Text style={styles.description}>Nombre Completo: <Text style={styles.subtitle}>{selectedPlayer.nombre_completo}</Text></Text>
<Text style={styles.description}>Posicion: <Text style={styles.subtitle}>{selectedPlayer.posicion}</Text> </Text>
<Text style={styles.description}>Edad: <Text style={styles.subtitle}>{selectedPlayer.edad}</Text></Text>
<Text style={styles.description}>Nacionalidad: <Text style={styles.subtitle}>{selectedPlayer.nacionalidad}</Text></Text>
</View>
</ScrollView>
);
}
;
export const screenOptions = navData => {
return {
headerTitle: navData.route.params.nombre,
}
};
const
styles = StyleSheet.create({
image: {
width: '100%',
height: 300,
},
subtitle: {
fontSize: 16,
textAlign: 'justify',
marginVertical: 20,
fontWeight:'normal',
},
description: {
fontSize: 16,
textAlign: 'center',
marginVertical: 20,
fontWeight: 'bold',
},
dataContainer:{
width: '80%',
alignItems: 'center',
marginHorizontal: 40,
},
actions: {
marginVertical: 10,
alignItems: 'center',
},
});
export default ProductDetailScreen
;```
This is my Action File:
import ResultadoEstadistica from '../../models/estadistica/resultadoEstadistica';
import PlayerEstadistica from '../../models/estadistica/playerEstatisticData';
import Cards from '../../models/estadistica/cards';
import Games from '../../models/estadistica/games';
import Goals from '../../models/estadistica/goals';
export const SET_JUGADORES = 'SET_JUGADORES';
export const SET_ESTADISTICA = 'SET_ESTADISTICA';
export const fetchJugadores = () => {
return async (dispatch) => {
//any async code here!!!
try {
const response = await fetch(
'https://alianzafc2021-default-rtdb.firebaseio.com/jugadores.json'
);
if (!response.ok) {
throw new Error('Algo salio Mal!');
}
const resData = await response.json();
const loadedJugadores = [];
for (const key in resData) {
loadedJugadores.push(
new Jugador(
key,
resData[key].altura,
resData[key].apellido,
resData[key].edad,
resData[key].fecha_nacimiento,
resData[key].iso_code,
resData[key].imagen,
resData[key].lugar_nacimiento,
resData[key].nacionalidad,
resData[key].nombre_completo,
resData[key].nombre_corto,
resData[key].nombres,
resData[key].numero,
resData[key].pais,
resData[key].peso,
resData[key].player_id,
resData[key].posicion
)
);
}
dispatch({ type: SET_JUGADORES, players: loadedJugadores });
} catch (err) {
throw err;
}
};
}
export const fetchEstadistica = player_id => {
return async (dispatch) => {
//any async code here!!!
try {
const response = await fetch(
`https://api-football-v1.p.rapidapi.com/v2/players/player/${player_id}.json`,
{
method: 'GET',
headers: {
'x-rapidapi-key': Here goes my API KEY,
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'useQueryString': 'true'
}
}
);
if (!response.ok) {
throw new Error('Algo salio Mal!');
}
const resData = await response.json();
const loadesApiResult = [];
console.log('***Impresion desde la accion***');
console.log(resData);
console.log('***Fin de Impresionc***');
//Arrays de la Estadistica del Jugador
const loadedEstadistica = [];
const loadedCards = [];
const loadedGoals = [];
const loadedGames = [];
for (const key in resData) {
loadesApiResult.push(
new ResultadoEstadistica(
resData[key].results,
resData[key].players
)
);
}
const apiData = loadesApiResult.players;
for (const key in apiData) {
loadedEstadistica.push(
new PlayerEstadistica(
apiData[key].player_id,
apiData[key].player_name,
apiData[key].firstname,
apiData[key].lastname,
apiData[key].number,
apiData[key].position,
apiData[key].age,
apiData[key].birth_date,
apiData[key].birth_place,
apiData[key].birth_country,
apiData[key].nationality,
apiData[key].height,
apiData[key].weight,
apiData[key].injured,
apiData[key].rating,
apiData[key].team_id,
apiData[key].team_name,
apiData[key].league_id,
apiData[key].league,
apiData[key].season,
apiData[key].captain,
apiData[key].shots,
apiData[key].goals,
apiData[key].passes,
apiData[key].duels,
apiData[key].dribbles,
apiData[key].fouls,
apiData[key].cards,
apiData[key].penalty,
apiData[key].games,
apiData[key].substitutes,
)
);
}
const playerDataGames = loadedEstadistica.games;
for (const key in playerDataGames) {
loadedGames.push(
new Games(
playerDataGames[key].apperences,
playerDataGames[key].minutes_played,
playerDataGames[key].lineups
)
);
};
const playerDataGoals = loadedEstadistica.goals;
for (const key in playerDataGoals) {
loadedGoals.push(
new Goals(
playerDataGoals[key].total,
playerDataGoals[key].conceded,
playerDataGoals[key].assists,
playerDataGoals[key].saves
)
);
};
const playerDataCards = loadedEstadistica.cards;
for (const key in playerDataCards) {
loadedCards.push(
new Cards(
playerDataCards[key].yellow,
playerDataCards[key].yellowred,
playerDataCards[key].red
)
);
};
dispatch({ type: SET_ESTADISTICA, estadistica: loadesApiResult, goles: loadedGoals, juegos: loadedGames, tarjetas: loadedCards });
} catch (err) {
throw err;
}
};
};```
Finally this is my Redux Reducer just incase:
import { SET_JUGADORES, SET_ESTADISTICA } from "../actions/jugadores";
const initialState = {
availablePlayers: [],
estadistica: [],
playerGoals: [],
playerCards: [],
playerGames: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case SET_JUGADORES:
return {
...state,
availablePlayers: action.players,
};
case SET_ESTADISTICA:
return{
...state,
estadistica: estadistica,
playerGoals: action.goles,
playerCards: action.tarjetas,
playerGames: action.juegos
};
}
return state;
};
Sorry for the Format but is giving me some issues; Any Ideas what my Be the Problem?
Thank you.
there are a few issues with your screen code, so i recommend simplifying the logic to make sure it works before adding anything thing else.
replace this:
const loadEstad = useCallback (async (param) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(userActions.fetchEstadistica(param));
} catch (err){
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
setIsLoading(true);
loadEstad(estadId).then(() => {
setIsLoading(false);
});
}, [dispatch, loadEstad]);
console.log(estadId);
console.log(goles);
with this:
useEffect(()=>{
if (estadId) dispatch(userActions.fetchEstadistica(estadId));
},[estadId]);
Assuming your reducer/action code is correct, then this should call the api everytime the params estadId changes. The loading/refresh should be set in the reducer instead on the screen component.
a few things to keep in mind:
Don't await for dispatch.
console.log on state variables outside of promise resolving code block won't work.
This below won't work. instead, you should set the loading variable as a redux variable that gets updated after the API comes back with data.
loadEstad(estadId).then(() => {
setIsLoading(false);
});
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as Location from 'expo-location'
import Permissions from "expo-permissions";
export default function App() {
const [address, setAddress] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
useEffect(() => {
console.log(errorMsg)
console.log(address)
}, []);
const _getLocationAsync = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== "granted") {
setErrorMsg = "Permission to access location was denied";
}
const location = await Location.reverseGeocodeAsync({});
const address = await Location.reverseGeocodeAsync(location.coords);
address;
_getLocationAsync();
};
let text = "Waiting...";
if (errorMsg) {
text = setErrorMsg;
} else if (address) {
text = setAddress[0].city;
}
return (
<View style={styles.container}>
<Text style={styles.text}>{text}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#bcbcbc',
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontSize: 20
}
});
I have this code to get the city location in react-native, but I don't know what else I should do to get the location. I'm trying this for a while but I'm changing so many things and don't know exactly what I did...
I'd appreciate if someone could help me a little here
I used Geolocation from '#react-native-comunity/geolocation' which returns latitude and longitude.
Geolocation.getCurrentPosition(async (info) => {
const location = await getLocation(
info.coords.latitude,
info.coords.longitude,
);
...
And used google maps api for geocoding
export const getLocation = async (lat: number, long: number) => {
const apiKey = 'my api key'
return Api.get(
`https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${long}&key=${apiKey}`,
);
};
Hope this works for you
I got this error when using useSelector to access variable from global store of Redux.
But Redux store for specific screen is still available.
Here is my code for specific screen:
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
productDetailReducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(productDetailWatcher);
interface IProductDetailProps {}
const Product = () => {
const product = useSelector((state: IProductDetailState) => state.product);
const products = useSelector((state: IStoreState) => state.productsState.products);
const dispatch: Dispatch = useDispatch();
const handlePress = () => {
const product = {
id: 1,
thumbImage: 'https://aladin-today-bucket.s3.ap-southeast-1.amazonaws.com/sm/4bd144c7-896c-55db-b70d-7b5a0b2d4638.jpeg',
thumbHeight: 192,
productName: 'Váy xường xám mặc Trung Thu,Tết'
};
dispatch(GetProductDetailRequest(product));
}
console.log(`Product Detail 2 render with ${product}`);
console.log(`Products ${products}`);
return (
<View style={styles.container}>
<Button onPress={handlePress} title='Get Product Detail' />
{product && (
<View style={styles.productContainer}>
<Image style={styles.image} source={{ uri: product.thumbImage }} />
<Text style={styles.name}>{product.productName}</Text>
</View>
)}
</View>
);
};
const ProductDetail: React.FC<IProductDetailProps> = () => {
console.log("Product Detail rendering...");
return <Provider store={store}>
<Product />
</Provider>;
};
reducer for ProductDetail:
import { IProductDetailState, ProductDetailActions, ProductDetailActionType } from './types';
import { ProductModel } from 'models/Product';
import AsyncStorage from '#react-native-community/async-storage';
import { PersistConfig, persistReducer } from 'redux-persist';
const productDetailState: IProductDetailState = {
product: undefined,
loading: false,
}
export const productDetailReducer = (state = productDetailState, action: ProductDetailActions): IProductDetailState => {
switch(action.type) {
case ProductDetailActionType.GET_PRODUCT_DETAIL_REQUEST: {
console.log('Enter');
return {
...state,
loading: true
}
}
case ProductDetailActionType.GET_PRODUCT_DETAIL_SUCCESS: {
const productDetail: ProductModel = action.payload;
return {
...state,
product: productDetail,
loading: false
}
}
case ProductDetailActionType.GET_PRODUCT_DETAIL_FAILURE: {
return {
...state
}
}
default:
return {
...state
}
}
}
const persistConfig: PersistConfig<any> = {
key: 'ProductDetail',
whitelist: ['product'],
storage: AsyncStorage,
version: 1,
timeout: 0
};
export default persistReducer(persistConfig, productDetailReducer) as any;
Package I use:
"react-native": "0.61.4"
"react-redux": "^7.2.0"
"redux": "^4.0.5",
"redux-persist": "^6.0.0"
"redux-saga": "^1.1.3"
Does anyone have any solution? Thank a lot
It is not advised to use multiple stores but react redux connect can use a different store by providing a store prop when rendering the component that connect creates. Here is an example:
const { Provider, useSelector, connect } = ReactRedux;
const { createStore } = Redux;
const store1 = createStore((x) => x, {
message: 'store 1',
});
const store2 = createStore((x) => x, {
message: 'store 2',
});
const Messages = ({ message2 }) => {
//useSelector will use the prop value store from Provider
const message1 = useSelector((s) => s.message);
return (
<ul>
<li>{message1}</li>
<li>{message2}</li>
</ul>
);
};
//connect uses prop store={store2} or store={store1} when
// MessageContainer does not have a store prop
const MessageContainer = connect(({ message }) => ({
message2: message,
}))(Messages);
const App = () => {
return <MessageContainer store={store2} />;
};
ReactDOM.render(
<Provider store={store1}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
I'm building a React Native app.
My app has 5 Screens: Home (initialRouteName), DeckPage, QuestionPage, NewCardPage, NewDeckPage. (in this order)
I'm using Redux for state management. The state is updating from AsyncStorage.
The component that does the fetching is the class component "Home" by dispatching the "fetching" function in componentDidMount.
Component NewCardPage, NewDeckPAge are also updating the state with new content by dispatching the same fetching function as the Home when a button is pressed.
My problem appears when I want to delete a Deck component from inside DeckPage parent component. The function that does this job has this functionality: after removing the item from AsyncStorage, updates the STATE, and moves back to Screen HOME. The issue is that when I go back to HOME component the state doesn't update with the latest info from AsyncStorage.
This is not the case when I'm doing the same operation in the other 2 components NewCardPage, NewDeckPage.
I'll paste the code below:
import React, { Component } from "react";
import { connect } from "react-redux";
import { View, Text, StyleSheet, FlatList } from "react-native";
import Header from "../components/Header";
import AddDeckButton from "../components/AddDeckButton";
import DeckInList from "../components/DeckInList";
import { receiveItemsAction } from "../redux/actions";
class Home extends Component {
componentDidMount() {
this.props.getAsyncStorageContent();
}
renderItem = ({ item }) => {
return <DeckInList {...item} />;
};
render() {
const { items } = this.props;
// console.log(items);
const deckNumber = Object.keys(items).length;
return (
<View style={styles.container}>
<Header />
<View style={styles.decksInfoContainer}>
<View style={styles.deckNumber}>
<View style={{ marginRight: 50 }}>
<Text style={styles.deckNumberText}>{deckNumber} Decks</Text>
</View>
<AddDeckButton />
</View>
<View style={{ flex: 0.9 }}>
<FlatList
data={Object.values(items)}
renderItem={this.renderItem}
keyExtractor={(item) => item.title}
/>
</View>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getAsyncStorageContent: () => dispatch(receiveItemsAction()),
};
};
-----------DECKPAGE COMPONENT------------
import React from "react";
import { View, StyleSheet } from "react-native";
import Deck from "../components/Deck";
import { useSelector, useDispatch } from "react-redux";
import { removeItemAction, receiveItemsAction } from "../redux/actions";
import AsyncStorage from "#react-native-community/async-storage";
const DeckPage = ({ route, navigation }) => {
const { title, date } = route.params;
const questions = useSelector((state) => state.items[title].questions);
const state = useSelector((state) => state.items);
const dispatch = useDispatch();
// const navigation = useNavigation();
const handleRemoveIcon = async () => {
await AsyncStorage.removeItem(title, () => {
dispatch(receiveItemsAction());
navigation.goBack();
});
};
console.log(state);
return (
<View style={styles.deckPageContainer}>
<Deck
handleRemoveIcon={handleRemoveIcon}
title={title}
questions={questions}
date={date}
/>
</View>
);
};
-----------This is my ACTIONS file----------
import AsyncStorage from "#react-native-community/async-storage";
export const RECEIVE_ITEMS = "RECEIVE_ITEMS";
// export const REMOVE_ITEM = "REMOVE_ITEM";
export const receiveItemsAction = () => async (dispatch) => {
const objectValues = {};
try {
const keys = await AsyncStorage.getAllKeys();
if (keys.length !== 0) {
const jsonValue = await AsyncStorage.multiGet(keys);
if (jsonValue != null) {
for (let element of jsonValue) {
objectValues[element[0]] = JSON.parse(element[1]);
}
dispatch({
type: RECEIVE_ITEMS,
payload: objectValues,
});
} else {
return null;
}
}
} catch (e) {
console.log(e);
}
};
-----This is my REDUCERS file----
import { RECEIVE_ITEMS, REMOVE_ITEM } from "./actions";
const initialState = {
};
const items = (state = initialState, action) => {
switch (action.type) {
case RECEIVE_ITEMS:
return {
...state,
...action.payload,
};
// case REMOVE_ITEM:
// return {
// ...state,
// ...action.payload,
// };
default:
return state;
}
}
export default items;
-----This is my UTILS file----
import AsyncStorage from "#react-native-community/async-storage";
export const removeDeckFromAsyncStorage = async (title)=>{
try{
await AsyncStorage.removeItem(title);
}
catch(e){
console.log(`Error trying to remove deck from AsyncStorage ${e}`);
}
}
I'm trying to write the unit test for the Controller which is written in react-native and expo Following is the login method for which Unit test is failing
login.tsx
import React, { useState } from 'react';
import { StyleSheet, SafeAreaView, Text, Button, Image, View, Alert, TouchableWithoutFeedback, TouchableOpacity, NativeSyntheticEvent, TextInputChangeEventData, TextInput } from 'react-native';
import axios from "axios"
import { StackNavigationProp } from '#react-navigation/stack';
import { RouteProp } from '#react-navigation/native';
import { RootStackParamList } from '~/navigations/Navigations'
interface props {
navigation: StackNavigationProp<RootStackParamList, 'Login'>;
route: RouteProp<RootStackParamList, 'Login'>;
value: string;
name: string;
}
const instance = axios.create({
baseURL: '',
timeout: 3000,
timeoutErrorMessage: 'do again',
});
const Login: React.FC<props> = ({ value, name, ...props }) => {
const [inputValue, setInputValue] = useState({
email: "",
pwd: ""
});
const [errorMesseage, setErrorMesseage] = useState("");
const onChangeText = (e: string, name: string): void => {
let input = e;
setInputValue(prev => ({
...prev,
[name]: input,
}));
if (name === "email") {
const emailRegx = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*#[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{3}$/i;
const emailList = ["naver.com", "gmail.com", "daum.net", "nante.com", "hanmail.net"];
let eMailAddress = input.slice(input.indexOf('#') + 1, input.length);
let eMailboolean = emailList.includes(eMailAddress) && name === 'email';
setErrorMesseage(eMailboolean && emailRegx.test(input) ? "" : "eamil regx");
}
}
const loginButton = async () => {
if (errorMesseage.length === 0) {
props.navigation.navigate("Main");
try {
const emailPost = await instance.post("/user", {
"email": inputValue.email,
"password": inputValue.pwd
});
console.log(emailPost);
} catch (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
}
} else {
Alert.alert("email regx");
}
}
return (
<>
<SafeAreaView style={styles.container} >
<View>
<View>
<TextInput onChangeText={text => onChangeText(text, 'email')} placeholder="email" placeholderTextColor={"#eee"} value={inputValue["email"]} autoCorrect={false} secureTextEntry={false} autoCapitalize="none" />
</View>
<Text>{errorMesseage}</Text>
<View>
<TextInput onChangeText={e => onChangeText(e, 'pwd')} placeholder="pwd" placeholderTextColor={"#eee"} value={inputValue["pwd"]} autoCorrect={false} secureTextEntry={true} autoCapitalize="none" />
</View>
<TouchableOpacity style={[styles.loginButton, { opacity: (!errorMesseage) && inputValue.pwd ? 1 : 0.3 }]} disabled={(!errorMesseage) && inputValue.pwd ? false : true} onPress={loginButton} >
<Text style={styles.loginText}>login</Text>
</TouchableOpacity>
<TouchableWithoutFeedback
onPress={() => props.navigation.navigate("Find")}
>
<Text>pwd find</Text>
</TouchableWithoutFeedback>
</View>
</SafeAreaView >
</>
);
};
export default Login;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
loginButton: {
backgroundColor: "#0095F6",
},
loginText: {
textAlign: "center",
color: "#fff"
}
});
Unit Test for the above code is written in react-native and expo
login.test.tsx
import { TextInput, SafeAreaView, TouchableOpacity, TouchableWithoutFeedback, View, Text } from 'react-native';
import React from 'react';
import { render, cleanup, fireEvent } from 'react-native-testing-library';
import '#testing-library/jest-dom/extend-expect'
import TestRenderer from 'react-test-renderer';
import Login from "../src/screen/login/Index";
afterEach(cleanup);
const createTestProps = (props: Object) => ({
navigation: {
navigate: jest.fn()
},
...props
});
describe("Login page rendering test", () => {
jest.useFakeTimers()
let props: any;
let rerender: any;
let testRenderer: any;
let testInstance: any;
beforeEach(() => {
rerender = render(<Login {...props} />);
testRenderer = TestRenderer.create(<Login {...props} />);
testInstance = testRenderer.root;
});
test('renders Login component', () => {
const component = rerender.toJSON();
expect(component).toBeTruthy();
});
it("SafeAreaView renders", () => {
expect(testInstance.findAllByType(SafeAreaView).length).toBe(1);
});
it("View renders", () => {
expect(testInstance.findAllByType(View).length).toBe(5);
});
it("textInput renders", () => {
const expectedPlaceholder = ['email', 'pwd'];
expect(testInstance.findAllByType(TextInput).length).toBe(2);
expectedPlaceholder.forEach((text: string) => {
rerender.findByPlaceholder(text);
});
});
it("TouchableOpacity renders", () => {
const element = testInstance.findByType(TouchableOpacity).findByType(Text);
expect(testInstance.findAllByType(TouchableOpacity).length).toBe(1);
expect(element.props.children).toEqual('login');
});
it("TouchableWithoutFeedback renders", () => {
const element = testInstance.findByType(TouchableWithoutFeedback).findByType(Text);
expect(testInstance.findAllByType(TouchableWithoutFeedback).length).toBe(1);
expect(element.props.children).toEqual('pwd find');
});
});
describe('login page funtion test', () => {
let props: any;
let rerender: any;
beforeEach(() => {
props = {
onChangeText: jest.fn(),
}
rerender = render(<Login {...props} />);
emailInput = rerender.getByPlaceholder('email');
fireEvent.changeText(emailInput, 'ab');
});
it('email input change', async () => {
rerender.getByDisplayValue("ab");
expect(props.onChangeText).toHaveBeenCalledWith('ab');
expect(props.onChangeText).toHaveBeenCalledTimes(1);
})
})
error part
describe('login page funtion test', () => {
let props: any;
let rerender: any;
beforeEach(() => {
props = {
onChangeText: jest.fn(),
}
rerender = render(<Login {...props} />);
emailInput = rerender.getByPlaceholder('email');
fireEvent.changeText(emailInput, 'ab');
});
it('email input change', async () => {
rerender.getByDisplayValue("ab");
expect(props.onChangeText).toHaveBeenCalledWith('ab');
expect(props.onChangeText).toHaveBeenCalledTimes(1);
})
})
I'm getting the following error. Need to know what I'm missing here?
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: "ab"
Number of calls: 0
116 |
> 117 | expect(props.onChangeText).toHaveBeenCalledWith('ab');
| ^
118 | expect(props.onChangeText).toHaveBeenCalledTimes(1);
119 |
120 | })
library
"expo": "^37.0.12",
"jest-dom": "^4.0.0",
"react": "~16.9.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz",
"react-test-renderer": "^16.13.1",
"react-native-testing-library": "^2.1.0",