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

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!

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.

FlatList items re-rendering even with React.memo

I am trying to render a list of items in React Native with the FlatList component but every time I fetch new data it re-renders the who list of items even with React.memo.
Here is what my code looks like:
const data = [
{ _id: 1, text: 'Hello World' },
{ _id: 2, text: 'Hello' },
{ ... }
]
const renderItem = ({ item }) => (<Component item={item} />)
const loadMore = () => {
//Fetching data from db and adding to data array
}
<FlatList
data={data}
keyExtractor={item => item._id}
renderItem={renderItem}
onEndReached={loadMore}
removeClippedSubviews={true}
/>
Component.js
const Component = ({ item }) => {
console.log('I am rendering')
return (
<Text>{item.text}</Text>
)
}
const equal = (prev, next) => {
return prev.item.text === next.item.text
}
export default React.memo(Component, equal)
Every time the onEndReached function gets triggered and calls the loadMore function, all FlatList items get re-rendered, it console.log 'I am rendering' every single time and causes the error virtualizedlist you have a large list that is slow to update
Thanks to anyone who can help me!
I don't know why but I fixed it with an if statement in the equal function
//Changing this
const equal = (prev, next) => {
return prev.item.text === next.item.text
}
//To this
const equal = (prev, next) => {
if(prev.item.text !== next.item.text) {
return false;
}
return true
}
Hope this could help someone else.

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);

How to pass a list with objects from Server to FlatList?

I'm trying to create a FlatList that contains an array with objects that has an array from a server and I can't get it to work.
my error message:
TypeError: Cannot read proparty 'Data' of undefined
I can get it to work with my normal list that's not fetched from a Server. Heres the structure from my working list
[{"DATA":[{"filter_id":"44","filter_name":"filter 1"}, {"filter_id":"45","filter_name":"filter 2"},{"filter_id":"46","filter_name":"filter 3"},{"filter_id":"47","filter_name":"filter 4"},{"filter_id":"48","filter_name":"filter 5"}],"MESSAGE":"DATA FOUND","STATUS":200}]
My server list have the same structure but different values of filter_name and filter_id
here's my code:
constructor(props){
super(props);
this.state = {
data: [],
oldData: [{"DATA":[{"filter_id":"44","filter_name":"filter 1"},{"filter_id":"45","filter_name":"filter 2"},{"filter_id":"46","filter_name":"filter 3"},{"filter_id":"47","filter_name":"filter 4"},{"filter_id":"48","filter_name":"filter 5"}],"MESSAGE":"DATA FOUND","STATUS":200}],
page:1,
status: null,
isLoading: false,
}
}
getData = async () => {
const url = 'api/getFilter.php?page='+this.state.page+'&row_per_page=5';
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
data:this.state.data.concat(responseJson),
isLoading:false
});
})
}
componentDidMount() {
this.getData();
}
renderRow = ({item}) => {
console.log('item', item);
return (
<View style={styles.item}>
<Text>{item.filter_name}</Text>
</View>
)
}
render() {
console.log('state', this.state.data[0]);
console.log('oldstate', this.state.oldData[0]) // this
return (
<View style={styles.container}>
<FlatList
data={this.state.data[0].DATA}
renderItem={this.renderRow}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
Expo: https://snack.expo.io/#thesvarta/tenacious-sandwich
The issue is that on the initial rendering of your component this.state.data is empty, because we have to wait until getData returns any data. That's why you cannot access this.state.data[0].DATA at the beginning.
The solution is to update your getData function a little bit.
getData = async () => {
const url = 'http://ollenorstrom.se/ollenorstrom.se/avoka/api/getFilter.php?page='+this.state.page+'&row_per_page=5';
fetch(url).then((response) => response.json())
.then((responseJson) => {
// here we save the data, we want to access later.
console.log('responseJson', responseJson[0].DATA);
this.setState({
data:this.state.data.concat(responseJson[0].DATA),
isLoading:false
});
})
}
Now your data is directly stored in this.state.data. We now can simplify your render() function:
<FlatList
data={this.state.data} // simplified, passing an empty array at the beginning is ok
renderItem={this.renderRow}
keyExtractor={(item, index) => index.toString()}
/>
Working example:
https://snack.expo.io/HJ--GFlnN