Not understand on behavior - react-native

Currently, I am learning react native and still don't know why it happened.
I have a component A and component B.
In component A when handle success I will pass isSucceeded: true and false for the failure case.
but I don't know why when having success case it navigates to B screen and always show Fail text before showing Success text for 1 second
component A:
login() {
dataService.getService().then((response) => {
navigateService.navigate('B', {isSucceeded: true})
}).catch(error => {
navigateService.navigate('B', {isSucceeded: false})
});
}
In component B:
componentDidMount() {
this.state.isSucceeded = navigation.getParam('isSucceeded')
}
render() {
this.state.isSucceeded ? <View> <Text>Success</Text></View> :
<View> <Text>Fail</Text></View>
}
How can it show Success Text when having success case and fail for failed case
Thanks you.

You should do it in the constructor
constructor(props) {
super(props);
this.state = {
isSucceeded: props.navigation.getParam('isSucceeded'),
};
}
If you insist doing it in componentDidMount then you must use setState():
componentDidMount() {
const isSucceeded = this.props.navigation.getParam('isSucceeded');
this.setState({ isSucceeded });
}

Related

render is called before the asynchronous method has finished in react native

hi everyone i am recently learning react native and i have a synchronization problem sending data from one screen to another screen.
I have the parent page (UserList) that wants to send its status to the child page (RecyclerViewPage) but the render is called before the data is available.
export default class UserList extends Component {
constructor(props){
super(props);
this.state={
comment_state: [],
datetime: []
}
}
//getPost is a network call which gets and store the result in the state of the class
async getPost(){
var sid=this.props.route.params.value_sid
var did=this.props.route.params.value_did
const utils=new Utils();
const responseJson = await utils.getPost(sid,did)
const comment = (responseJson?.posts ?? []).map((data) => data.comment)
this.setState({comment_state:comment})
console.log("now i change state with new value")
}
componentDidMount(){
this.getPost()
}
render(){
return(
<RecyclerViewPage
comment={this.state.comment_state}
/>
)
}
}
and RecyclerViewPage code:
export default class RecyclerViewPage extends Component {
constructor(props) {
super(props);
console.log("i am in recyclerviewPage : ",this.props.comment)
}
render(){}
}
I tried to put some logs and the result is:
Log: "i am in recyclerviewPage: []"
Log: "now i change state with new value"
It appears as if render () was called before the asynchronous getPost method has finished. how can i synchronize my code in order to get the data first and then send it to the RecyclerViewPage daughter schemata?
See you can wait for the results and then show recyclerviewpage:
You can add a loader state while its being fetched:)
Hope it helps. feel free for doubts
export default class UserList extends Component {
constructor(props){
super(props);
this.state={
comment_state: [],
datetime: []
}
}
//getPost is a network call which gets and store the result in the state of the class
async getPost(){
var sid=this.props.route.params.value_sid
var did=this.props.route.params.value_did
const utils=new Utils();
const responseJson = await utils.getPost(sid,did)
const comment = (responseJson?.posts ?? []).map((data) => data.comment)
this.setState({comment_state:comment})
console.log("now i change state with new value")
}
componentDidMount(){
this.getPost()
}
render(){
return(
{!!comment_state && <RecyclerViewPage
comment={this.state.comment_state}
/> }
{!comment_state && <Text> Please wait its loading </Text>}
)
}
}

How to set dashboard as first screen after login in react-native

I am using react-native where my first screen is Welcome screen and I want to set dashboard on my first screen when the user is login.
Here is my code:
componentWillMount(){
var self = this;
AsyncStorage.getItem(AppStrings.contracts.IS_LOGGED_IN).then((json) =>{
try{
var userDetail = JSON.parse(json);
if(userDetail.isLoggedIn != undefined && userDetail.isLoggedIn == true){
Actions.dashboard();
}
}catch(e){
}
})
}
I set this code on the Welcome screen and its working fine in IOS. But in android issue is it shows the Welcome screen for 5 to 10 seconds before going to dashboard screen when the user is login.
Here I am using react-native-router-flux for the navigation bar.
Because AsyncStorage.getItem() is asynchronous, your render() function is being called BEFORE it has been fulfilled.
So the flow of your application is:
Call componentWillMount()
AsyncStorage.getItem()
render() - This is where you see your Welcome Screen for 5-10 seconds
AsyncStorage has been fulfilled - .then() and then the User gets redirected to the dashboard.
I would set an isLoaded flag in your state:
constructor() {
super();
this.state = {
isLoaded: false,
}
}
Then inside of your componentWillMount() function, set the value to true once AsyncStorage has fulfilled its Promise.
try {
var userDetail = JSON.parse(json);
if(userDetail.isLoggedIn != undefined && userDetail.isLoggedIn == true){
Actions.dashboard();
}
this.setState({ isLoaded: true });
}
And finally, I would add some sort of loading indicator inside of render() to show the User that your application is still performing some logic.
render() {
if(this.state.isLoading) {
return <Text>I am loading</Text>
} else {
return ...
}
}

How to avoid navigating to other screen multiple times

When press on any button on my React Native App to navigate to a different screen multiple times, then it will redirected to the next screen multiple times.
My sample code is:
// This is my button click event
myMethod()
{
this.props.navigation.navigate("ScreenName")
}
I am using react-navigation to navigate through my app.
How can I fix this behaviour?
I think there are a few ways this could be done. Perhaps recording when the navigation has occurred and preventing it from navigating multiple times.
You may also want to consider resetting hasNavigated after an amount of time etc as well.
// Somewhere outside of the myMethod scope
let hasNavigated = false
// This is my button click event
myMethod()
{
if (!hasNavigated) {
this.props.navigation.navigate("ScreenName")
hasNavigated = true
}
}
This react-navigation issue contains a discussion about this very topic, where two solutions were proposed.
The first, is to use a debouncing function such as Lodash's debounce that would prevent the navigation from happening more than once in a given time.
The second approach, which is the one I used, is to check on a navigation action, whether it is trying to navigate to the same route with the same params, and if so to drop it.
However, the second approach can only be done if you're handling the state of the navigation yourself, for example by using something like Redux.
Also see: Redux integration.
One of solution is custom custom components with adds debounce to onPress:
class DebounceTouchableOpacity extends Component {
constructor(props) {
super(props);
this.debounce = false;
}
_onPress = () => {
if (typeof this.props.onPress !== "function" || this.debounce)
return;
this.debounce = true;
this.props.onPress();
this.timeoutId = setTimeout(() => {
this.debounce = false;
}, 2000);
};
componentWillUnmount() {
this.timeoutId && clearTimeout(this.timeoutId)
}
render() {
const {children, onPress, ...rest} = this.props;
return (
<TouchableOpacity {...rest} onPress={this._onPress}>
{children}
</TouchableOpacity>
);
}
}
another: wrap onPress function into wrapper with similar behavior
const debounceOnPress = (onPress, time) => {
let skipCall = false;
return (...args) => {
if (skipCall) {
return
} else {
skipCall = true;
setTimeout(() => {
skipCall = false;
}, time)
onPress(...args)
}
}
}

Check state before render for react native

I need to get the user's name the first time the app is run. Then after I want to skip the first screen and go directly to the second screen.
I'm using AsyncStorage.getItem("first") in the first screen to check if this is the first boot. If not, navigate('SecondScreen').
The problem is that the first screen flash for half a second before going to the second screen. Is there a way to fix this?
Render a loader and check AsyncStorage until the data is loaded, then navitage or render your component conditionally like so:
constructor(){
super()
this.state = {
hasName: null, loaded: false,
}
}
componentDidMount(){
this.fetchName()
}
fetchName(){
AsyncStorage.getItem('first')
.then((e, s)=>{
this.setState({hasName: s ? true : false, loaded: true})
});
}
loading(){
return(<View><Text>Loading...</Text></View>)
}
renderLoaded(){
if (this.state.hasName){
return(...)
} else {
navigate('secondScreen')
return(...)
}
}
render(){
if (this.state.loaded){
return this.renderLoaded()
}
return this.loading()
}
Basicaly, you want to do something similar...
Render "loading" screen while loading data. When done, rerender with main application.
class Application extends React.Component {
state = {
ready: false,
user: null,
}
async componentWillMount() {
const user = await AsyncStorage.getItem("user");
this.setState({
user,
ready: true
})
}
render() {
if (this.state.ready === false) {
// render "booting" screen while reading data from storate or remote server
return <Boot />;
}
if (this.state.user === null) {
// render Login screen
return <Login />
}
// Render main navigation stack for app
return <NavigatorStack user={this.state.user} />
}
}

How to render component with ajax in 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()}
...
);