How to render component with ajax in react native? - react-native

My code is
const main = () => {
let caption;
AsyncStorage.getItem("XXX", (err, result) => {
caption = <View>...</View>
});
render (
...
{caption}
...
);
}
But I got an error as below.
RawText "" must be wrapped in an explicit <Text> component.

I'm going to assume that, based on your pseudo-code, you understand how to get data from AsyncStorage, that it's not a good idea to be using AsyncStorage inside your render function, and that you don't actually mean ajax but rather local storage.
But the error is showing up because you need to make sure you wrap text inside a <Text> element. If you look at this paragraph it says:
In React Native, we are more strict about it: you must wrap all the text nodes inside of a <Text> component; you cannot have a text node directly under a <View>.
EDIT:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: '',
};
}
componentDidMount() {
AsyncStorage.getItem('XXX', (err, result) => {
// #TODO: You should handle errors too
this.setState({
data: result.text,
});
});
}
render() {
// Returning null will not render anything
// Once the results come in, it will update automatically
if (!this.state.data) return null;
// Raw text must be wrapped in Text
return (
<Text>{this.state.data}</Text>
);
}
}

You can try to stock data in a state and display your component whit a function:
AsyncStorage.getItem("XXX", (err, result) => {
this.setState({result:result})
});
function element(){
if([check if your state is not null])
return(
<View>... {this.state.result} ...</View>
)
}
render (
...
{this.element()}
...
);

Related

undefined returns from react-native-document-picker

I am attempting to use the 'react-native-document-picker' module in React Native to build image file 'picking' functionality.
import React, { Component } from "react";
import { Button, Dimensions, Image, Platform, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
import DocumentPicker from 'react-native-document-picker';
export default class Images extends Component {
constructor(props) {
super(props);
this.state = {
photo: null
};
}
//*****FUNCTIONS
ChoosePhoto = async () => {
try {
const res = await DocumentPicker.pick({ type: [DocumentPicker.types.images], });
//Provide the type of file you want user to pick
console.log('File Selection Response: ' + JSON.stringify(res));
console.log('File Selection Response JSON: ' + res);
console.log(
res.uri,
res.type, // mime type
res.name,
res.size
)
this.setState({ photo: res }); // Setting the state to show single file attributes
} catch (err) {
this.setState({ photo: null });
if (DocumentPicker.isCancel(err)) {
alert('Canceled'); //user canceled the document selection
} else {
alert('Selection Error: ' + JSON.stringify(err)); //Unknown Error
} //Handling any exception (if any)
} //try routine
};
//*****
render() {
return (
<View style={styles.main}>
<TouchableOpacity
style={styles.buttonStyle}
onPress={this.ChoosePhoto}
>
<Text style={styles.buttonTextStyle}>Select Image</Text>
</TouchableOpacity>
</View>
); //return
} //render
} //class 'Images' Component
I do not receive any errors upon running the code above. The problem is that all after 'picking' a file the 'console.log' statements for returned JSON values are "undefined".
In other words the command for "const res = await DocumentPicker.pick({ type: [DocumentPicker.types.images], });" seems to run OK and the initial 'console.log' (that utilizes a 'JSON.stringify' statement) returns info like this:
File Selection Response: [{"size":144672,"fileCopyUri":null,"name":"IMG-20221228-WA0002.jpg","type":"image/jpeg","uri":"content://com.android.providers.media.documents/document/image%3A2483"}]
However any other 'console.log' statement that is there (without the 'JSON.stringify' statement) just reads as "undefined"...therefore the "this.setState({ photo: res });" also seems to be setting "undefined" as the value for the "photo" variable.
Does anybody know why the JSON values are returned as "undefined" after the 'pick'? How am I to set the "this.state.photo" variable correctly using the information that is returned from the 'pick'? I thank you in advance for any advice.
react-native-document-picker return DocumentPickerResponse[] which is array even if you set
allowMultiSelection: false
so you have to use it like an array. res.uri will be undefined but res[0].uri will get you values.
according to documentation :
Breaking in v6: pick returns a Promise<Array> instead of Promise. If you were using pick, change those usages to pickSingle.
if you want to pick only one item at a time then i suggest you should use ** pickSingle** which will return you an object instead of Array.

wait for the asynchronous method to finish and pass the result to another screen

hi everyone i'm just learning react native and i have a question. How do I wait for an asynchronous method to finish and then pass the result to another screen?
My code is:
export default class AAA extends Component {
constructor(props){
super(props);
this.state={
comments: [],
datetime: []
}
}
//getPost is a network call which gets and store the result in the state of the class
async getPost(){
const utils=new Utils();
const responseJson = await utils.getPost("ok","yes")
const comment = (responseJson?.posts ?? []).map((data) => data.comment)
this.setState({comments:comment})
console.log("now i change state with new value")
}
componentDidMount(){
this.getPost()
}
render(){
return(
<NextScreen
comment={this.state.comments}
/>
)
}
}
I want the getPost () method to finish and then go to the other screen, passing the result of the getPost () method. I tried to use the componentWillMount method as well but it is deprecated. How can I do?
You can use conditional rendering in this case as follows:
render() {
if (this.state.comments.length === 0) {
return null;
}
return (
<NextScreen
comment={this.state.comments}
/>
)
}
The initial state comments contains an empty array, thus it is never undefined. However, its length is equal to zero until the async call returns with its result.
You can use react-router params
if you are fetching data in the splash screen, you want to pass it to the next page.
in then() or after await expression
you can learn about this in React router, pass data when navigating programmatically?

TypeError: Undefined is not a function (evaluating 'this.state...map')

**In this my code, I got an error which says 'this.state.leader.map' is not a function.
I searched on web about my problem but none of them solved my issue.
This is my code which is below. I hope, code is clear to understand easly**
import React, { Component } from 'react';
import { View, Text, FlatList } from 'react-native';
import DOMParser from 'react-native-html-parser';
import axios from 'axios';
export default class header extends Component {
state = { leader: [] };
componentWillMount() {
fetch('url')
.then(response => {
if (response.ok) {
return response;
}else {
let error = new Error('Error ');
error.response = response;
throw error;
}
},
error => {
let errmess = new Error(error.message);
throw errmess;
})
.then(response => response.text())
.then(leaders => {
const str = leaders.substring(76);
const str2 = str.substring(0, str.length - 9);
this.setState({ leader: str2 });
})
.catch(error => {
this.setState({ errMessage: error.message });
});
}
renderall() {
return this.state.leader.map(alb => <Text>{alb.Ref}</Text>
}
render() {
console.log(this.state.leader);
return (
<View>
{this.renderall()}
</View>
);
}
}
You issue is with the fact that you are setting a string to the value of leader in your state.
If we look at your code you are taking a substring of leaders. To do this leaders must be a string. You are then saving that string to your leader value in state.
.then(leaders => {
const str = leaders.substring(76);
const str2 = str.substring(0, str.length - 9); // <- this is a string
this.setState({ leader: str2 });
})
In your state you are setting leaders in the following way
state = {
leaders: [] // <- this is an array
}
Individually doing these things are fine, but there is a disconnect between what you are doing in your fetch request and what you then want to do in your renderAll method as you cannot use .map on a string.
renderall() {
return this.state.leader.map(alb => <Text>{alb.Ref}</Text>
// .map requires this.state.leader to be an array but you
// have now changed it to a string so it won't work
}
Either you need to change what you are storing in leader in your fetch request so that it is an array. Or you need to change what is happening in your renderAll function.
XML parsing
If your XML is the same as you put in your comment
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="url">[{"Ref":"IHR1900299","Unvan":"max"},{"Ref":"IHR1900298","Unvan":"max2"}] </string>
Then you could use a simple regex to capture what you want and then use JSON.parse to convert it into json.
Once you have your xml as a string, you could do this:
// I'm hardcoding it as a string, because I don't have your api to call.
let xmlString = '<?xml version="1.0" encoding="utf-8"?> <string xmlns="url">[{"Ref":"IHR1900299","Unvan":"max"},{"Ref":"IHR1900298","Unvan":"max2"}] </string>';
let jsonString = xmlString.match(/\[.*\]/);
let json = JSON.parse(jsonString);
console.log(json)
console.log(json[0].Ref)
React component usual methods (like componentWillMount or render) are automatically bound to this by React, contrary to your custom method renderAll. Add the following to your class:
constructor(props) {
super(props);
this.renderAll = this.renderAll.bind(this);
}
Useful links:
Docs on the bind keyword
Binding to this in React
Related question: React Native: bind and 'this'?
Related question: How to bind(this) when passing a function as a prop in react native?
This happen because the fetch function is asynchronous. So, all you need is to add a simple condition and passing this:
renderall() {
return this.state.leader.map(alb => <Text>{alb.Ref}</Text>
}
render() {
console.log(this.state.leader);
return (
<View>
{
(this.state.leader?this.state.leader.length>0:false)&&
this.renderall(this)
}
</View>
);
}
Your renderAll method needs to return a single JSX element in order to be correct. Additionally I reformatted the method to use arrow functions to avoid all those nasty binding issues. Also: you need to provide a unique key to each of the items.
renderall = () => {
return (
<View>
{this.state.leader.map(alb => {
return <Text key={alb.Ref}>{alb.Ref}</Text>;
})}
</View>
}

Validate React Native Component with Asynchronous Work

I have a basic component that calls a webservice during the componentDidMount phase and overwrites the contents value in my state:
import React, {Component} from 'react';
import {Text} from "react-native";
class Widget extends Component {
constructor() {
super();
this.state = {
contents: 'Loading...'
}
}
async componentDidMount() {
this.setState(...this.state, {
contents: await this.getSomeContent()
});
}
render() {
return (
<Text>{this.state.contents}</Text>
)
}
async getSomeContent() {
try {
return await (await fetch("http://someurl.com")).text()
} catch (error) {
return "There was an error";
}
}
}
export default Widget;
I would like to use Jest snapshots to capture the state of my component in each one of the following scenarios:
Loading
Success
Error
The problem is that I have to introduce flaky pausing to validate the state of the component.
For example, to see the success state, you must place a small pause after rendering the component to give the setState method a chance to catch up:
test('loading state', async () => {
fetchMock.get('*', 'Some Content');
let widget = renderer.create(<Widget />);
// --- Pause Here ---
await new Promise(resolve => setTimeout(resolve, 100));
expect(widget.toJSON()).toMatchSnapshot();
});
I'm looking for the best way to overcome the asynchronicity in my test cases so that I can properly validate the snapshot of each state.
If you move the asynchronous call out of setState, you can delay setState until the network call has resolved. Then you can use setState's optional callback (which fires after the state change) to capture the state.
So, something like this:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
()=>expect(this.toJSON()).toMatchSnapshot()
);
}
UPDATE:
If you want to specify the validation outside of the component, you could create a prop, say, stateValidation to pass in a the validation function:
jest('loading state', async () => {
fetchMock.get('*', 'Some Content');
jestValidation = () => expect(widget.toJSON()).toMatchSnapshot();
let widget = renderer.create(<Widget stateValidaton={jestValidation}/>);
});
then use the prop in the component:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
this.props.stateValidaton
);
}

realm.js - Accessing object of type Contact which has been invalidated or deleted with NO stack trace

Would (really) appreciate help on this one.
I have a realm listView which navigates away to a detailed view.
The detailed view is deleting the entry from the original list view and navigate back. I have registered listeners on the realm DB change to update the content of the list view.
Here is the code I'm using, which, after delete get an exception - "Accessing object of type Contact which has been invalidated or deleted" after the navigation occurs.
Does anyone have an idea why?
Also, it seems that the change listener (updateContactsFromDB) is called twice, while deleting just one object - ideas?
10x
ContactPage.js:
export default class ContactsPage extends Component {
updateContactsFromDB(){
console.log("ContactsPage:updateContactsFromDB()");
let contacts = Realm.objects('Contact');
this.setState({
dataSource: this.state.dataSource.cloneWithRows(contacts.snapshot()),
});
}
constructor(props) {
console.log("ContactsPage:constructor()");
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => (r1 !== r2)});
let contacts = Realm.objects('Contact');
this.state = {
dataSource: ds.cloneWithRows(contacts.snapshot()),
};
this.updateContactsFromDB = this.updateContactsFromDB.bind(this);
}
componentWillMount(props){
console.log("ContactsPage:componentWillMount");
Realm.addListener('change', this.updateContactsFromDB);
}
componentWillUnmount(props){
console.log("ContactsPage:componentWillUnmount");
Realm.removeListener('change', this.updateContactsFromDB);
}
render() {
console.log("ContactsPage:render()");
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(contact) => (
<TouchableOpacity onPress={ () => this.props.navigation.navigate('ContactNotesPage', { contact: contact}) }>
<Text>test Contact</Text>
</TouchableOpacity>
)
}
/>
);
}
}
ContactNotesPage.js:
export default class ContactNotesPage extends Component {
constructor(props) {
console.log("ContactNotesPage:constructor");
super(props);
}
render(){
console.log("ContactNotesPage:render()");
const { params } = this.props.navigation.state;
return (
<TouchableOpacity onPress={ () => {
console.log("ContactNotesPage:delete");
Realm.write(() => { Realm.delete(params.contact);});
this.props.navigation.navigate('ContactsPage');
}
}>
<Text>DeleteContact</Text>
</TouchableOpacity>
)
}
};
// main.js
const MainStack = StackNavigator({
ContactsPage: {
screen: ContactsPage,
},
ContactNotesPage:{
screen: ContactNotesPage,
},
});
export default MainStack;
Seems like it's a bug in realm/react-navigation. when passing a parameter which is a realm object - if you delete this object your next navigation will fail.
A bug is already open at https://github.com/realm/realm-js/issues/1031
This happens when you get an object from realm (using realm.objects('collection')) and use realm.close() function on it. Either use Object.assign method or destructuring object or array approach to mitigate this problem. Like below,
const realmObject = realm.objects('collection');
const obj = [...realmObject] or {...realmObject[0]}; // the later one when filtered is used on realmObject.
My solution was that I did not call realm.close() because I was constantly engaged with the database.
https://realm.io/docs/javascript/0.14.0/api/Realm.html#close
i used useFocusEffect from #react-navigation/native after delete item in array for update list array like this
//state to check get data ability:
const [getDataAbility, setGetDataAbility] = useState(true);
//useFocusEffect
useFocusEffect(
React.useCallback(() => {
if (getDataAbility === true) {
// handle data
}
}, [ dependencies... ]),
);
// catch error ""accessing obj of..."
try {
console.log(pickingItem.id);
} catch (error) {
setGetDataAbility(false);
console.log('err picking: ', error);
}
-> solve
This is work for me:
async function updateRealm() {
const realm = Realm.open({
schemas: [
/*myschemas*/
]
});
realm.addListener('change', () => {
/* consult function */
});
}