How to get updated wrapper after axios call - react-native

I have a function that looks like the following and I am trying to test the rowDoubleClicked function.
I mock the axios resolved value and I can see that the getAccountData function is being covered which should mean the update to dataArray.isLoading would be false.
however in my test when I debug the wrapper. It always hits the if statement that renders the loading div instead of the grid component and Im trying to figure out how to make it render the grid so that i can call the rowDoubleClicked function.
I've tried updating the wrapper, but it stays the same.
I've also tried doing an awat waitForElement on the component but it just gets timed out
import React, { useState } from 'react';
import axios from 'axios';
const MyComponent = (props) => {
let grid;
const dataArray = {
errorText: '',
rowData: '',
isLoading: true,
};
const [data, setData] = useState();
if (undefined !== data) {
dataArray.errorText = data.errorText;
dataArray.isLoading = data.isLoading;
dataArray.rowData = data.rowData;
}
const setShow = props.functions;
const getAccountData = async () => {
await axios
.get(props.endpoint)
.then((result) => {
dataArray.rowData = result;
})
.catch((error) => {
dataArray.errorText = error;
});
dataArray.isLoading = false;
setData(dataArray);
};
const handleClose = () => setShow(false);
const rowDoubleClicked = () => {
//some action
};
if (dataArray.errorText !== '') {
grid = (
<div>
<p>Error</p>
</div>
);
} else if (dataArray.isLoading) {
getAccountData();
grid = (
<div className="loading">
<p>Loading</p>
</div>
);
} else if (dataArray.rowData !== '') {
grid = <Grid handleRowDoubleClicked={rowDoubleClicked} />;
}
return (
<div>
<Modal visible={props.show} closable onCancel={handleClose}>
<div>{grid}</div>
</Modal>
</div>
);
};
export default MyComponent;
MyComponentView
import React from 'react'
import MyComponent from ''
const MyComponentView = (props) => {
const [select, setSelect] = React.useState('')
const [show, setShow] = React.useState(false)
const [selectedSearchBy, setSearchBy] = React.useState('')
const [selectedValue, setSearchByValue] = React.useState('')
const handleSearchIconClick = () => {
setShow(true)
}
const handleOnChange = (e) => {
setSearchBy(e.selectedOptionVal)
setSearchByValue(e.value)
}
return (
<div>
<form
action={`${endpoint`}
method='post'
onSubmit={handleSubmit}
>
<input type='hidden' id='searchBy' name='searchBy' value={selectedSearchBy} />
<input type='hidden' id='searchValue' name='searchValue' value={selectedValue} />
<Button data-testid='accessButton' id='accessButton' block color='primary'>
Search
</Button>
</form>
{show && (
<MyComponent
show
functions={setShow}
onModalApplyClick={handleApply}
endpoint={endpoint}
/>
)}
</div>
</div>
)
}
export default MyComponentView
here is my current test
it('performs double click on grid', async () => {
let wrapper;
let grid;
axios.get.mockResolvedValue(dataJSON);
wrapper = mount(
<MyComponent {...props} show>
<Modal>
<Grid {...gridProps} />
</Modal>
</MyComponent>
);
grid = wrapper.find(Grid);
wrapper.update();
await waitForElement(() => expect(grid).toBeTruthy());
grid.invoke('handleRowDoubleClicked')();
await act(() => Promise.resolve());
});

So it seems like your axios.get.mockResolvedValue is not working as intended. In such situations, I, personally, just use axios-mock-adapter.
Also, seems like waitForElement has been deprecated. How about a simple setTimeout with jest's done()?
import MockAdapter from 'axios-mock-adapter';
it('...', (done) => {
const mock = new MockAdapter(axios);
mock.onPost().reply(200, dataJSON);
//your test's logic
setTimeout(() => {
expect(grid).toBeTruthy();
}, 1000); //or any reasonable delay
});

Related

making several api calls slows down react native app

So I am calling getUserProducts() (to show the updated list) whenever a product is added to the list and whenever a product is deleted from the list. But I've noticed when I add several items to the list through the dropdown, some times the product doesn't show in the list/getUserProducts isn't called (and then if I add another product it'll then show the previous added product) I'm assuming its because I'm calling it every time I add and that's making it slow? Is there a way I can work around this to optimize it?
const App = () => {
const [products, setProducts] = useState<ProductType[] | []>([]);
const [userProducts, setUserProducts] = useState<ProductType[] | []>([]);
const [toggleCheckBox, setToggleCheckBox] = useState(false);
const [value, setValue] = useState(' ');
const [isFocus, setIsFocus] = useState(false);
const [visible, setVisible] = useState(false);
const [productId, setProductId] = useState('');
const [product, setProduct] = useState('');
const [num, setNum] = useState('');
const [amount, setAmount] = useState('');
const submitForm = async () => {
let body;
body = {
product_id: productId,
product: product,
num: num,
amount: amount,
};
const response = await postProduct(body);
if (response == undefined) {
return;
}
};
const getProducts = async () => {
try {
const response = await axios.get('http://192.168.1.32:3000/api/products');
setProducts(response.data);
} catch (error) {
// handle error
alert('no');
}
};
const getUserProducts = async () => {
try {
const response = await axios.get(
'http://192.168.1.32:3000/api/user_products',
);
setUserProducts(response.data);
} catch (error) {
// handle error
alert('no');
}
};
React.useEffect(() => {
getProducts();
getUserProducts();
console.log(userProducts);
}, []);
return (
<>
<Provider>
<Dialog visible={visible} onDismiss={() => setVisible(false)}>
<DialogHeader title="Add to your list" />
<DialogContent>
<Dropdown
style={[styles.dropdown, isFocus && {borderColor: 'blue'}]}
data={products}
search
maxHeight={300}
labelField="product"
valueField="num"
placeholder={!isFocus ? 'Select item' : '...'}
searchPlaceholder="Search..."
value={value}
onFocus={() => setIsFocus(true)}
onBlur={() => setIsFocus(false)}
onChange={item => {
setValue(item.num);
setProductId(item.product_id);
setProduct(item.product);
setNum(item.num);
setIsFocus(false);
}}
/>
<TextInput
label="quantity"
variant="standard"
onChangeText={text => {
setAmount(text);
console.log(text);
}}
/>
</DialogContent>
<DialogActions>
<Button
title="Cancel"
compact
variant="text"
onPress={() => setVisible(false)}
/>
<Button
title="Add"
compact
variant="text"
onPress={() => {
setVisible(false);
submitForm();
console.log('added');
getUserProducts();
}}
/>
</DialogActions>
</Dialog>
{userProducts.length > 0 ? (
userProducts.map(userProduct => (
<ListItem
title={
userProduct.product +
' x' +
userProduct.amount +
' num: ' +
userProduct.num
}
onPress={async () => {
await deleteProduct(userProduct.product_id);
console.log('deleted');
getUserProducts();
ToastAndroid.show('Done', ToastAndroid.SHORT);
}}
trailing={
<CheckBox
disabled={false}
value={toggleCheckBox}
onValueChange={newValue => setToggleCheckBox(newValue)}
/>
}
/>
))
) : (
<Text>Nothing in your list yet</Text>
)}
</Provider>
</>
);
};
export default App;
I'm pretty certain that you aren't experiencing "lag" but race conditions.
See, when you create an item, you call submitForm() and getuserProducts() and both are async functions. Depending on how long the individual requests take, or how their execution gets scheduled getuserProducts() may very well finish before submitForm(). The new data then only reaches the server after you fetched the (not so) new data.
Consider the following code (it's just a simplified version of your app):
import React, { useState } from 'react';
interface ProductType {
id: number;
name: string;
}
export default function NotWorking() {
const [products, setProducts] = useState<ProductType[]>([]);
const createProduct = async () => {
await serverCreateProduct(`product: ${products.length}`);
console.log('product created');
};
const getProducts = async () => {
setProducts(await serverGetProducts());
console.log('products loaded...');
};
return (
<div>
<button
onClick={() => {
createProduct();
getProducts();
}}
>
Add
</button>
<h2>List:</h2>
<ul>
{products.map((product) => (
<li key={String(product.id)}>{product.name}</li>
))}
</ul>
</div>
);
}
const _userProducts: ProductType[] = [];
async function serverGetProducts() {
return new Promise<ProductType[]>((resolve) => {
setTimeout(() => {
resolve([..._userProducts]);
}, 300);
});
}
async function serverCreateProduct(name: string) {
return new Promise<void>((resolve) => {
setTimeout(() => {
_userProducts.push({ id: Math.random(), name });
resolve();
}, 500);
});
}
If you execute it, you will see that getProducts() finishes before createProduct(), so that result cannot include the new data.
You should await both of them, in order to get what you want, for example:
const createProduct = async () => {
await serverCreateProduct(`product: ${products.length}`);
console.log('product created');
setProducts(await serverGetProducts());
console.log('products loaded');
};
// ...
<button onClick={() => createProduct()}>Add</button>
See the code working here.

Display all posts from database

I have a Firestore collection, schemed as follows:
posts{
uid{
userPosts{
postID{
creation:
postText:
}
}
}
}
I want to display all of the posts, so I've made the corresponding queries and saved them in posts - an array of all the posts that I later iterate through.
The problem with the way I do it is that it keeps adding the same posts every render. So I've tried to set the array each time, but that way the code never passes through these posts && posts.length > 0 condition.
I'm really new to RN and JS in general, but what I was expecting is
Nothing to show here
at first, and then the list of posts.
The complete component:
import { Text, Pressable, FlatList, SafeAreaView } from "react-native";
import { globalStyles } from "../../styles/global";
import React, { useState, useEffect } from "react";
import { db } from "../../../firebase";
import Post from "../../API/Post";
import { collection, getDocs } from "firebase/firestore";
const FeedScreen = ({ navigation }) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPostData = async () => {
setPosts([]); // ---> Without this line the posts keeps adding each render
const q = collection(db, "posts");
const docSnap = await getDocs(q);
docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
tmpSnap.docs.map(async (element) => {
setPosts((prev) => {
prev.push(element.data());
return prev;
});
});
});
};
getPostData().catch(console.error);
return;
}, []);
return (
<SafeAreaView style={globalStyles.global}>
{posts && posts.length > 0 ? (
<FlatList
data={posts}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
keyExtractor={(item, index) => index.toString()}
/>
) : (
<Text>Nothing to show here</Text>
)}
<Pressable
title="edit"
onPress={() => {
navigation.navigate("CreatePost", { navigation });
}}
style={globalStyles.plus_btn}
>
<Text style={globalStyles.plus_btn_text}>+</Text>
</Pressable>
</SafeAreaView>
);
};
export default FeedScreen;
As said, I'm new to this so I'd love an explanation of what actually happens and how to do it properly.
I think the prev value of setPosts will always be [] since it does not immediately update if you call it. A standard way to do it is to call setPosts at the end of your function. Can you try this one?
useEffect(() => {
const getPostData = async () => {
const q = collection(db, "posts");
const docSnap = await getDocs(q);
const promises = docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
return tmpSnap.docs.map((element) => element.data());
});
const arrayOfPosts = await Promise.all(promises);
let newPosts = [];
arrayOfPosts.forEach((posts) => {
newPosts = [...newPosts, ...posts];
});
setPosts(newPosts);
};
getPostData().catch(console.error);
return;
}, []);

filter is not setting state as intended, how do i create async if statement?

I'm fetching data from my backend and storing it as a state. Inputs from local storage are then supposed to be filtering the backend data and rendering the results. However it seems my .filter isn't setting state as anything (undefined). However, if i save my code after a duration of time it then operates as intended until i refresh.
What is happening, do i need to create a async statement?
import "./Home.css";
import axios from 'axios';
const Tables = () => {
const [data, setData] = useState();
const [isloading, setIsLoading] = useState(true);
const [result, setResult] = useState();
const test = async () => {
await axios.get('http://localhost:5000/').then((res)=>{
setData(res.data.data)
setIsLoading(false)
})
const storedSize = localStorage.getItem('size');
const storedFeel = localStorage.getItem('feel');
const storedCost = localStorage.getItem('cost');
const storedStyle = localStorage.getItem('style');
const storedRoom = localStorage.getItem('room');
const info = {size: storedSize, feel: storedFeel, cost: storedCost, style: storedStyle, room: storedRoom};
const keys = Object.keys(info);
const values = Object.values(info);
const x = values;
const type = keys;
if(type[3]==="style") {
const filterProcess = data?.filter( i => {
return i.style===x[3];
})
setResult(filterProcess)
};
};
useEffect(() => {
test();
}, []);
if (isloading){
return <h2>Loading...</h2>
};
const clear = () => {
window.location = '/First';
localStorage.clear();
};
const renderTable = () => {
return result?.map((hero)=>{
return (
<div className="card mx-2" style={{width: '18rem'}} key={hero.data_id}>
<img className="card-img-top" src={hero.image} alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">{hero.name}</h5>
<p className="card-text">Feel: {hero.feel}</p>
<p className="card-text">Style: {hero.style}</p>
<p className="card-text">Cost: £{hero.cost}</p>
<p className="card-text">Room: {hero.room}</p>
<p className="card-text">Size: {hero.size}</p>
Go to link
<br/>
</div>
</div>
)
})
};
return (
<Fragment>
{renderTable()}
<button className="btn btn-primary" onClick={clear}>New Search</button>
</Fragment>
);
};
export default Tables;```
Resolved! - I moved my data state inside the useEffect dependencies.

PDFtron & react Error: Two instances of WebViewer were created on the same HTML element. Please create a new element for each instance of WebViewer

I am trying to show in the app that I built in React a PDF file using PDFtron and encounter the following error: Two instances of WebViewer were created on the same HTML element. Please create a new element for each instance of WebViewer.
my code is:
import { url } from "../../../../config.json";
import React, { useState, useEffect, useRef } from "react";
import { getProject } from "../../../../services/projectService";
import { useParams } from "react-router-dom";
import WebViewer from "#pdftron/webviewer";
import { getCurrentUser } from "../../../../services/userService";
import { Link, Redirect } from "react-router-dom";
import { deleteImage } from "../../../../services/projectService";
const MyContracts = () => {
const [project, setProject] = useState({});
const [counter, setCounter] = useState(0);
const [files, setFiles] = useState([]);
const { id } = useParams();
// const viewerDiv = useRef();
const user = getCurrentUser();
const [viewerUrl, setViewerUrl] = useState(`${url}/files/testing.pdf`);
const viewer = document.getElementById("viewer");
useEffect(() => {
getProject(id)
.then(res => {
setProject(res.data);
setFiles(res.data.files.contracts);
})
.catch(error => console.log(error.message));
}, []);
useEffect(() => {
if (files.length > 0) {
WebViewer(
{
path: `${url}/lib`,
initialDoc: `${url}/files/testing.pdf`,
fullAPI: true,
},
viewer
).then(async instance => {
const { docViewer } = instance;
docViewer.getDocument(viewerUrl);
});
}
}, [files, viewerUrl]);
if (!user) return <Redirect to="/private-area/sign-in" />;
if (user && user.isAdmin | (user._id === project.userID))
return (
<div className="container">
</div>
{/********** PDF VIEWER ************/}
<div className="web-viewer" id="viewer"></div>
{/* <div className="web-viewer" ref={viewerDiv} id="viewer"></div> */}
{/********** PDF Gallery ************/}
{files !== undefined && (
<>
<h2 className="text-rtl h3Title mt-2">בחר קובץ</h2>
<select
id="select"
className="col-12 text-rtl px-0"
onChange={e => setViewerUrl(e.target.value)}>
{files.map((file, index) => (
<option value={`${url}${file.url}`} key={index}>
{file.name}
</option>
))}
</select>
</>
)}
</div>
);
};
export default MyContracts;
What am I doing wrong and how can I fix it?
I see that you are trying to load multiple instances of WebViewer:
useEffect(() => {
if (files.length > 0) {
WebViewer(
{
path: `${url}/lib`,
initialDoc: `${url}/files/testing.pdf`,
fullAPI: true,
},
viewer
).then(async instance => {
const { docViewer } = instance;
docViewer.getDocument(viewerUrl);
});
}
}, [files, viewerUrl]);
Webviewer cannot be instantiated more than once in the same HTML element. If you need a completely different instance, you can hide or remove the HTML element and create a new one to hold the new instance.
That being said, if you just need to load another document, I would recommend using the loadDocument API. You can read more about it here as well.
​

Next js Firebase Auth phone number invisible recaptcha

Nextjs Firebase Phone Auth
First attempt useEffect()
useEffect(() => {
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha', {
'size': 'invisible',
'callback': (response) => {
console.log("This is not fired on loading", response)
}
})
}, [])
return (
<>
<div id="recaptcha"></div>
<button onClick={clicked}> Click me </button>
</>
)
This runs, however the recaptcha doesn't work... User is forced to pick fire hydrants.
Second attempt: React Component
Inspiration: https://stackoverflow.com/a/63860925/7451631
Import this to Login page
class Recap extends Component {
constructor(props) {
super(props);
this.signIn = this.signIn.bind(this);
}
componentDidMount() {
window.reCaptchaVerifier = new firebase.auth.RecaptchaVerifier(this.recaptcha, {
'size': 'invisible',
'callback': function (response) {
console.log("Magic", response)
}
})
}
signIn() {
firebase.auth().signInWithPhoneNumber(phoneNumber, window.reCaptchaVerifier).catch((error) => {
console.log(error)
})
}
render() {
return (
<>
<div ref={(ref) => this.recaptcha = ref} onClick={this.signIn}> Clik meeeee </div>
</>
)
}
}
Works! I got a ugly solution while typing up this question. If anyone knows how to make it nicer or can explain why the first attempt did not work that would be dope.
here is my solutions:
import { createFirebaseApp } from '#utils/firebase';
import { getAuth, PhoneAuthProvider, RecaptchaVerifier, signInWithCredential } from 'firebase/auth';
import { useState } from 'react';
export default function Example() {
const app = createFirebaseApp();
const auth = getAuth(app);
const [code, setCode] = useState('');
const [verificationId, setVerificationId] = useState('');
const signInWithPhone1 = async () => {
const applicationVerifier = new RecaptchaVerifier(
'sign-in-button',
{
size: 'invisible',
},
auth,
);
const provider = new PhoneAuthProvider(auth);
const vId = await provider.verifyPhoneNumber('+855012000001', applicationVerifier);
setVerificationId(vId);
};
const verify = async () => {
const authCredential = PhoneAuthProvider.credential(verificationId, code);
const userCredential = await signInWithCredential(auth, authCredential);
console.log('verify: ', userCredential);
};
return (
<>
<button id="sign-in-button" onClick={signInWithPhone1}>
SignIn With Phone1
</button>
<div>
<input type="text" value={code} onChange={(v) => setCode(v.target.value)} />
<button onClick={verify}>Verify</button>
</div>
</>
);
}