Run componentDidMount function again React Native - react-native

i try fetch info from server and while fetching not complete show searching Component and when fetch done show information...everything is OK...But when NET offline show searching component and when turning on NET i want show a button "Try Again" and when click on "FetchData" function run again.
constructor(){
super();
this.state={
isLoading:true,
dataSource:null,
dataError:false
}
}
componentDidMount(){
FetchData = () => {
return fetch(SuperUrl+'/info')
.then((response)=>response.json())
.then((responseJson)=>{
this.setState({
isLoading:false,
dataSource: responseJson
})
})
.catch((error)=>{this.setState({dataError:true})})
}
FetchData()
}
render() {
if(this.state.dataError)
{
<View>
<Buttom onpress={()=>{FetchData()}}>
<Text>Try Again</Text>
<Button>
</View>
}
if(this.state.isLoading)
{
return(
<Container>
<StatusBar backgroundColor={'#3949ab'}/>
<Searching/>
<JaFooter list={{backgroundColor:'#3949ab', color:'#ffffff'}}/>
</Container>
)
}
else
{
let regionName = this.state.dataSource.map((value,key)=>{
return(
<ListItem key={key} >
<Text key={key} style={styles.cityName}>{value.name}</Text>
</ListItem>
)
})

Your title is very misleading as componentDidMount should run once. What you want is totally different so I'll explain. Since you are asking for something that goes against the pattern of RN you are risking getting your answer closed. Anyway..
Fetch does not support a timeout natively. But you can workaround it by running two promises. One for your fetch, and another for the timeout. This is pseudo code, you are gonna have to learn about Promise.race and how setTimeout works.
const promise1 = fetch;
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject(new TimeoutError()), 30000);
})
Promise.race([promise1, promise2])
.then(() => {
//You got a response from the server
})
.catch(err => {
//Probably timeout
if (err.code == 'Timeout')
this.setState({loading: false, showTryAgain: true})
})
Promise.race will run both in parallel but if the network request for the fetch ends first, it will ignore the timeout which is what you want.

Related

Re render flat list when data change cause infinite loop React Native

I have two screens. Approve List and Approve Detail. When data approved in Approve Detail, page navigate to Approve List. Then approved data should disapear from FLatList. How to remove FlatList item when data approved? or how to re render FlatList when data change? Here is my code:
Approve List:
const Approve = ({ navigation }) => {
const [rekomendasi, setRekomendasi] = useState({})
// other code
const getRekomendasi = async (token, bagian) => {
try {
const response = await sippApi.get(`/penjaminan?bagian=${bagian}`, {
headers: {
Auth: token
}
});
setRekomendasi(response.data.data)
console.log(rekomendasi)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getToken();
getUserData()
getRekomendasi(token, userData.bagian);
}, [setToken, setUserData, rekomendasi]); // if I pass rekomendasi here, make infinite loop on api request
return (
<FlatList
onRefresh={() => onRefresh()}
refreshing={isFetching}
removeClippedSubviews
style={{ marginTop: 2 }}
data={rekomendasi}
keyExtractor={rekom => rekom.penjaminan.nomor_rekomendasi}
renderItem={({ item }) => {
return (
<TouchableOpacity onPress={() => navigation.navigate("ApproveDetail", { id: item.penjaminan.nomor_rekomendasi, bagian: userData.bagian })}>
<ApproveList
plafond={item.value}
kantor={item.nama_kantor}
nomor_rekomendasi={item.nomor_rekomendasi}
produk={item.skim}
/>
</TouchableOpacity>
)
}}
showsHorizontalScrollIndicator={false}
/>
)
}
If I pass value on second argument on UseEffect, it cause infinite loop on API request. If not, my FlatList cant re render when data change. What should I do?
Thanks for help
You have to remove the rekomendasi dependency in the useEffect to avoid infinite loop, it's only for init data :)
What is the purpose of onRefresh function in the FlatList ? Instead you could put the getRekomendasi function to trigger a new call and your data will be updated
try to separate the functions to two useEffects
useEffect(() => {
//<-- write your getToken() and getUserDate() here
getToken();
getUserData()
}, []);
useEffect(() => {
const getRekomendasi = async (token, bagian) => {
try {
const response = await sippApi.get(`/penjaminan?bagian=${bagian}`, {
headers: {
Auth: token
}
});
setRekomendasi(response.data.data)
console.log(rekomendasi)
} catch (error) {
console.log(error)
}
}
getRekomendasi(token, userData.bagian);
},[token,userData.bagian]);
Problem solved by using useFocusEffect
useFocusEffect(
React.useCallback(() => {
getRekomendasi(token, userData.bagian)
}, [token, userData.bagian])
);

Jest + Formik - Warning: You called act(async () => ...) without await

I am struggling with act errors when it comes to testing my React Native application using JEST and testing-library.
I have a simple Formik form and I am trying to test if the validation works.
My screen I am testing:
const SignInScreen: React.FC = () => {
const { translations } = useContext(LocalizationContext);
const [signIn, { isLoading, isError }] = useSignInMutation();
const initialValues: SignInRequest = {
name: '',
password: ''
};
const validationSchema = Yup.object({
name: Yup.string()
.required(translations['required'])
.max(15, ({max}) => translations.formatString(
translations['validationNCharOrLess'], { n: max })),
password: Yup.string()
.required(translations['required'])
});
const handleSubmit = async (values: SignInRequest, formikHelpers: FormikHelpers<SignInRequest>) => {
await signIn(values)
.unwrap()
.catch(e => {
if ('data' in e && e.data &&
'errors' in e.data && e.data.errors)
{
formikHelpers.setErrors(mapErrors(e.data.errors));
}
})
}
return (
<SafeAreaView
testID={tiConfig.SAFE_AREA_VIEW}
style={{ flex: 1 }}>
<View
testID={tiConfig.SIGN_IN_SCREEN}
style={styles.container}>
<View>
<Text>{translations['signIn']}</Text>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}>
{
({ values, errors, handleSubmit, handleChange }) => (
<View>
<Input
testID={tiConfig.SIGN_IN_USERNAME_INPUT}
value={values.name}
placeholder={translations['username']}
onChangeText={handleChange('name')}
errorMessage={errors.name} />
<Input
testID={tiConfig.SIGN_IN_PASSWORD_INPUT}
value={values.password}
placeholder={translations['password']}
onChangeText={handleChange('password')}
errorMessage={errors.password}
secureTextEntry />
{
isError ?
<View>
<Text testID={tiConfig.SIGN_IN_SERVER_ERROR}>
{ translations['somethingWentWrongTryAgainLater'] }
</Text>
</View>
: null
}
<Button
testID={tiConfig.SIGN_IN_SUBMIT}
title={translations['signIn']}
onPress={handleSubmit}
loading={isLoading} />
</View>
)
}
</Formik>
</View>
</View>
</SafeAreaView>
);
}
My test:
// tiConfig is a json with my test id constants
test.only("Sign in username field validates correctly", async () => {
const component = render(<SignInScreen />);
const usernameInput = await component.findByTestId(tiConfig.SIGN_IN_USERNAME_INPUT);
// A bit weird way to find the error text with a nesting but it works for now
const errorMessage = usernameInput
.parent!.parent!.parent!.parent!.parent!.parent!.findByType(Text);
const submit = component.getByTestId(tiConfig.SIGN_IN_SUBMIT);
fireEvent.press(submit);
await waitFor(() => expect(errorMessage.props.children).toBe(translations.required));
fireEvent.changeText(usernameInput, "username");
await waitFor(() => expect(errorMessage).toBeEmpty());
fireEvent.changeText(usernameInput, "toolongusernameshouldntbeallowed");
await waitFor(() => expect(errorMessage).not.toBeEmpty());
});
Warning:
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
at registerError (node_modules/react-native/Libraries/LogBox/LogBox.js:172:15)
at errorImpl (node_modules/react-native/Libraries/LogBox/LogBox.js:58:22)
at console.Object.<anonymous>.console.error (node_modules/react-native/Libraries/LogBox/LogBox.js:32:14)
at printWarning (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:68:30)
at error (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:44:5)
at node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15297:13
at tryCallOne (node_modules/promise/lib/core.js:37:12)
I get this warning 3 times
Without waitFor my test doesn't pass as all of the expect need to be awaited. I tried to wrap fireEvents in act as well, but according to few blog posts from Kent C. Dodds we shouldn't wrap fireEvent in act so although the test passes I still get the warnings.
Any ideas how I can fix this?
I faced similar issues. Wrapping fireEvent with await waitFor did the trick for me. So, when you call fireEvent.changeText make sure to wrap it with await waitFor
In your case,
test.only("Sign in username field validates correctly", async () => {
... Other test suites
await waitFor(() => {
fireEvent.changeText(usernameInput, 'username');
});
await waitFor(() => {
fireEvent.changeText(usernameInput, 'toolongusernameshouldntbeallowed');
});
});
Well, wrapping fireEvent in act actually solved the issue and I am not getting warnings, if has a different answer that would work or explanation why this work I would be delighted to hear it.

How to properly use AsyncStorage in a Movie WatchList

First of all,i tried to make a watchlist for movies,a page that contains movies that you add.I can't get understand async storage and how it works within my code.
I've tried different tutorials,but I still don't get to make it work.I tried with the code that official react-native page has on their site,but with no results.
item2ADD = this.props.navigation.getParam('movie');
_retrieveData = async () => {
try {
await AsyncStorage.setItem('#MySuperStore:key', 'I like to save it.');
} catch (error) {
console.warn("data has not been saved");
}
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
console.warn("data has been loaded");
console.warn(value);
}
} catch (error) {
console.warn("data has not been loaded");
}
};
constructor(props) {
super(props);
this.state = {
data : [] ,
};
}
async componentDidMount() {
const data = this._retrieveData();
this.setState({ data });
}
render() {
if(this.state.data!==null) {
return (
<View style={styles.container}>
<Text style={styles.title}>Watchlist</Text>
<TouchableOpacity style={styles.backBtn} onPress={() => {
this.props.navigation.goBack()
}}>
<Image style={styles.backIMG} source={menuImages.back}/>
</TouchableOpacity>
<FlatList
data={this.state.data}
numColumns={2}
renderItem={({item}) =>
<View style={styles.fList}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Details', {movie: item})}>
<Image style={styles.img} source={{uri: item.coverUrl}}/>
</TouchableOpacity>
<Text style={styles.movieTitle}>{item.title}</Text>
<Text
style={styles.movieDate}>{moment.unix(Math.floor(parseInt(item.releaseDate)) / 1000).format("DD/MM/YYYY")}</Text>
</View>
} keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
else
{
return <Text>FAILED TO LOAD</Text>
}
}
}
I just want to know how can I implement AsyncStorage to store my added movies from the item2ADD,it contains only 1 item that i need to store in order to make a watchlist.(the item which contains all details that i need for a movie).
I want to save all the movies with asyncstorage then show them in that flatlist.
I keep getting :
invariant violation tried to get frame for out of range index nan(on android simulator) when i click to add a movie to the list.
I'm not sure what is the problem you are asking but there is a few things that maybe causing the problem.
1- In your function _retrieveData you don't return anything from it.
I'm not sure what you want to return but what you could do is return the value from the AsyncStorage.
_retrieveData = async () => {
try {
await AsyncStorage.setItem('#MySuperStore:key', 'I like to save it.');
} catch (error) {
console.warn("data has not been saved");
}
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
console.warn("data has been loaded");
console.warn(value);
}
// returning the value.
return value
} catch (error) {
console.warn("data has not been loaded");
}
};
2- You are setting state with the data from _retrieveData without waiting for it.
In you componentDidMount you set the data from _retrieveData but because it's a promise, you need to use await to get the data
async componentDidMount() {
// added await
const data = await this._retrieveData();
this.setState({ data });
}
3- The data you provide to FlatList needs to be an array.
When you call pass data={this.state.data} to FlatList, it needs to be an array, if it's not an array, you will have several problems.
Make sure that when you return the value from _retrieveData, it's an array.
// value needs to be an array
const value = await AsyncStorage.getItem('TASKS');
You should also notice that to setItem, it need to be parsed to a json, this means doing something like AsyncStorage.setItem('NAME_OF_YOUR_KEY', JSON.stringify(DATA_YOU_WANT_TO_STORE); and to getItem it need to be parsed to a javascript object, this means doing something like
const jsonValue = await AsyncStorage.getItem('NAME_OF_YOUR_KEY');
const value = JSON.parse(jsonValue);

React-Native; Objects are not valid as a react child

I'm getting an error at dataSource: responseJson.event_array, when I remove this line everything works fine however, when I compare it to other peoples code it's the same. It does reach the server, because I do not get the alert message.
componentDidMount() {
fetch('valid url')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
dataSource: responseJson.event_array,
isLoading: false
});
})
.catch((error) => {
alert('Could not reach the server!')
});
}
What am I doing wrong, The error is
Invariant Violation: Objects are not valid as a React child (found:
object with keys {_40,_65,_55,_72})
'valid url' points to a json file and is indeed an object, I'm trying to use the data to compare it to other data stored to use a if function which will decide whether the item of FlatList will be rendered or not
<FlatList
data={this.state.dataSource}
renderItem={this.renderItem}
keyExtractor={item => item.name}
/>
another piece of code
renderItem = async ({item}) => {
var approved = false;
var name_string = await AsyncStorage.getItem('planner_name');
if(name_string != null){
var name_array = name_string.split(',');
name_array.map(name => {
if(name == item.name){
approved = true;
}
})
}
startReading = ({item}) => {
this.setState({
reading: true,
item: item
});
}
if(approved){
return (
<TouchableOpacity style={{flex: 1, flexDirection: 'row', marginBottom: 5}} onPress={() => this.startReading({item})}>
<Text>{item.name}</Text>
</TouchableOpacity>
);
} else {
return null
}
}
If you have any question feel free to ask.
Thank you for your time.
This:
object with keys {_40,_65,_55,_72}
is an unresolved promise. I suspect the issue is that this.renderItem is an async function which I suspect is not allowed. async is essentially going to wrap the result of your function in a Promise, which then must be resolved. Since renderItem does not expect a Promise, it does not know to resolve one and as such is simply returning an unresolved Promise object for each item in your data source.
Instead you could try using an async function expression:
renderItem = ({item}) => {
const get_name_string = async function(key){
const name_string = await AsyncStorage.getItem('key')
return name_string
}
get_name_string('planner_name').then(name => {
// the rest of your renderItem function should be in this block
})
}
or simply using .then syntax on the call to AsyncStorage
renderItem = ({item}) => {
AsyncStorage.getItem('planner_name').then(name => {
// again, the rest of your function should be in this block
})
}
or better yet find another pattern that doesn't require you to use asynchronous code in your renderItem function. Hope this helps, let me know if you have any questions!

How to retrieve fetch data in my native app

I would like to know how I can view data from my fetch query in my app.
I have a node that is fetched from React native and I want to display the response.
The node part;
app.get('/balance', function(req, res){
//calling the function
res.jsonp('0');
console.log("CRYPTO CALLED");
});
The react function;
_getBalanceFromApiAsync() {
fetch('http://192.168.1.100:3000/ballance')
.then(response => response.json())
.then(responseJson => {
console.log(responseJson);
return responseJson;
})
.catch((error) => {
console.error(error);
});
}
I can see the result in the node console as well as the react console. But where it doesn't work is here.
Native app;
<Text style={styles.getStartedText}>Your Wallet Balance</Text>
<Text> {this._getBalanceFromApiAsync()}</Text>
</View>
The function is getting executed but I would like to display the returned value and as it is the text field remain empty
Thank you
Its simple you need to setState for re-rendering the component. Try doing this
constructor(props){
super(props)
this.state = {
textData: ''
}
}
componentDidMount(){
this.getBalanceFromApiAsync()
}
getBalanceFromApiAsync() {
fetch('http://192.168.1.100:3000/ballance')
.then(response => response.json())
.then(responseJson => {
console.log(responseJson);
this.setState({
textData: responseJson
})
})
.catch((error) => {
console.error(error);
});
}
<Text style={styles.getStartedText}>Your Wallet Balance</Text>
<Text> {this.state.textData}</Text>
</View>