React Native Reload Screen A In Back action - ios

I Have ScreenA To Click Next ScreenB Then back To Screen A Not Call Function componentWillMount()
ScreenA -> Next -> ScreenB -> Back() -> ScreenA
How to Reload Rout Screen in Back Action
Class ScreenA
import React from "react";
import { Button, Text, View } from "react-native";
class ScreenA extends Component {
constructor(props){
super(props)
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
})
}
}
componentWillMount() {
fetch(MYCLASS.DEMAND_LIST_URL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId:'17'})
})
.then((response) => response.json())
.then((responseData) => {
if (responseData.status == '1') {
var data = responseData.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data),
});
}
})
.done();
}
onPress = () => {
this.props.navigate("ViewB");
};
render() {
return (
<View>
<Text>test</Text>
<Button title="Next" onPress={this.onPress} />
</View>
);
}
}
Class ScreenB
import React from "react"
import { Button } from "react-native"
class ScreenB extends Component {
render() {
const {goBack} = this.props.navigation;
return(
<Button title="back" onPress={goBack()} />
)
}
}

Class ScreenA
import React from "react";
import { Button, Text, View } from "react-native";
class ScreenA extends Component {
constructor(props){
super(props)
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
})
}
}
componentWillMount() {
this.getData()
}
getData() {
fetch(MYCLASS.DEMAND_LIST_URL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId:'17'})
})
.then((response) => response.json())
.then((responseData) => {
if (responseData.status == '1') {
var data = responseData.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data),
});
}
})
.done();
}
onPress = () => {
this.props.navigate("ViewB", { onSelect: this.onSelect, getData: () => this.getData() });
};
render() {
return (
<View>
<Text>test</Text>
<Button title="Next" onPress={this.onPress} />
</View>
);
}
}
Class ScreenB
class ScreenB extends Component {
componentWillUnmount() {
this.props.navigation.state.params.getData()
}
render() {
const {goBack} = this.props.navigation;
return(
<Button title="back" onPress={goBack()} />
)
}
}

As react-navigation using stack. When we navigate to another screen, current screen remain as we have, another screen show on current screen. That means competent is still there. Component will reload (recycle) only if component creating again but at this point component will not change. We can reload data and re-render data.
By default react navigation not providing any api for onBack event. But we can achieve our goal by some tricks.
Way 1
use one function to handle onBack event and pass it to navigated screen
class ScreenA extends Component {
onBack() {
// Back from another screen
}
render() {
const { navigation } = this.props
return (
<Button title="Open ScreenB" onPress={() => navigation.navigate('ScreenB', { onBack: this.onBack.bind(this) })} />
)
}
}
// In this ScreenB example we are calling `navigation.goBack` in a function and than calling our onBack event
// This is not a safest as if any device event emmit like on android back button, this event will not execute
class ScreenB extends Component {
goBack() {
const { navigation } = this.props
navigation.goBack()
navigation.state.params.onBack(); // Call onBack function of ScreenA
}
render() {
return (
<Button title="Go back" onPress={this.goBack.bind(this)} />
)
}
}
// In this ScreenB example we are calling our onBack event in unmount event.
// Unmount event will call always when ScreenB will destroy
class ScreenB extends Component {
componentWillUnmount() {
const { navigation } = this.props
navigation.state.params.onBack();
}
render() {
return (
<Button title="Go back" onPress={() => this.props.navigation.goBack()} />
)
}
}
Way 2
Try react-navigation listener https://reactnavigation.org/docs/en/navigation-prop.html#addlistener-subscribe-to-updates-to-navigation-lifecycle
We have some limitation in this. We have blur and focus event. You can put your logic on focus. Whenever you will back from another screen, ScreenA will focus and we can execute our logic. But there is one issue, this will execute every time when we got focus whatever first time or we minimize and reopen application.
Way 3
https://github.com/satya164/react-navigation-addons#navigationaddlistener
I'm not sure about this way, I didn't tried.

Related

Admob reward ads don't work on ios on Expo App

On App.js I have initialized AdMobRewarded as following:
if (Platform.OS === 'ios') {
AdMobRewarded.setAdUnitID('ca-app-pub-xxx/xxx');
} else {
AdMobRewarded.setAdUnitID('ca-app-pub-xxx/xxx');
}
And here is the class:
export default class App extends React.Component {
state = {
fontsLoaded: false,
};
render() {
const { fontsLoaded } = this.state;
if (!fontsLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => this.setState({ fontsLoaded: true })}
/>
);
}
return (
<Provider store={store}>
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
<CommonComponents />
</Provider>
);
}
}
Inside the CommonComponents I have put the listener for AdMobRewarded:
useEffect(() => {
AdMobRewarded.addEventListener('rewardedVideoDidRewardUser', () => {
setState({
hintModalVisible: true,
adIsLoading: false,
mainMenuVisible: false,
});
});
return () => {
AdMobRewarded.removeAllListeners();
};
}, []);
setState is actually not React setState, it's a redux action I have implemented:
const setStateAction = (obj, sceneName) => {
const type = sceneName ? `${sceneName}_SET_STATE` : 'SET_STATE';
return { ...obj, type };
};
Without the rewardedVideoDidRewardUser listener, calling setState does open the Modal and everything is fine.
hintModalVisible is used for Modal isVisible prop, which opens and closes the Modal.
On Android everything works as expected, but there is a strange behavior on iOS. The ad shows for a second and automatically closes, and the Hint Modal doesn't open.
Here is the function that requests and shows the ad. It is present in all screens of the app:
showHint = async () => {
const { setState } = this.props;
try {
setState({
mainMenuVisible: false,
});
await AdMobRewarded.requestAdAsync();
await AdMobRewarded.showAdAsync();
} catch (e) {
setState({
hintModalVisible: true,
mainMenuVisible: false,
});
}
};
It's an open source project, so you can the code here
The problem was with React Native Modal.
setState({
hintModalVisible: true,
adIsLoading: false,
mainMenuVisible: false,
});
This block of code should have closed the main menu modal and open the hint modal. But it seems that on IOS you cannot do this simultaneously. So this is how I handled it.
useEffect(() => {
AdMobRewarded.addEventListener('rewardedVideoDidRewardUser', () => {
setTimeout(() => {
setState({
hintModalVisible: true,
});
}, 1000);
});
return () => {
AdMobRewarded.removeAllListeners();
};
}, []);
And took the closing of main menu modal in the ad requesting function:
const requestHint = useCallback(async () => {
try {
setState({
mainMenuVisible: false,
});
await AdMobRewarded.requestAdAsync();
await AdMobRewarded.showAdAsync();
} catch (e) {
setState({
mainMenuVisible: false,
});
setTimeout(() => {
setState({
hintModalVisible: true,
});
}, 500);
}
}, [setState, hintModalVisible]);
So this is not concerned to the Admob Rewarded. It is more a React Native Modal bug. https://github.com/react-native-community/react-native-modal/issues/192

React-Native navigation.addListener is not a functio

Is there a way I can fix this without using redux?
This is only happening on iOS, on android the AddListener works perfectly fine without it.
I have a component and I call the props.navigation.addListener on the componentDidMount functon.
Some code to help understand exactly where it breaks:
componentDidMount(){
var _this = this;
this.willBlurListener = this.props.navigation.addListener('willBlur', () => {
_this.timer.clearTimeout();
});
this.willFocusListener = this.props.navigation.addListener('willFocus', () => {
_this._action();
});
AppState.addEventListener('change', this._handleAppStateChange);
}
And then I use the component like this:
<Inactivity name='SomeNameView' navigation={ this.props.navigation }>
{this.renderDetails()}
</Inactivity>
Can you please try to use withNavigation function, it returns a HOC that has navigation in it props so you don't have to pass from the parent component to the child:
I created a simple app that uses this concept that probably can help you:
import React from 'react';
import {
View,
Text,
Button,
} from 'react-native';
import {
createStackNavigator,
withNavigation,
} from 'react-navigation';
class SomeComponent extends React.Component {
componentDidMount() {
this.willBlurListener = this.props.navigation.addListener('willBlur', () => {
this.someAction();
})
}
someAction() {
console.log('Some action is called!');
}
componentWillUnmount() {
this.willBlurListener.remove();
}
render() {
return (
<View>
<Text>Some Component</Text>
<Button
title={'Open settings'}
onPress={() => this.props.navigation.navigate('Settings')}
/>
</View>
)
}
}
const SomeComponentWithNavigation = withNavigation(SomeComponent);
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Home'
}
render() {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<SomeComponentWithNavigation/>
<Text>Welcome to home screen!</Text>
</View>
)
}
}
class SettingsScreen extends React.Component {
static navigationOptions = {
title: 'Settings'
}
render() {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Welcome to settings screen!</Text>
</View>
)
}
}
export default createStackNavigator(
{
Home: HomeScreen,
Settings: SettingsScreen,
},
);
I have used import { useNavigation } from '#react-navigation/native'; to achieve this. This could work for you as well.
Sample code example
import { useNavigation } from '#react-navigation/native';
class CurrentOrderClass extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.onFocusSubscribe = this.props.navigation.addListener('focus', () => {
// Your code
});
}
componentWillUnmount() {
this.onFocusSubscribe();
}
.
.
.
.
function CurrentOrder(props) {
const navigation = useNavigation(props)
return <CurrentOrderClass {...props} navigation={navigation} />
}
}
export default CurrentOrder;
You can also check to React Native docs https://reactnavigation.org/docs/navigation-events/
I found this a bit tricky and after looking into it for a bit, I come up with the following solution. Note that is tested on React Navigation 5.x.
import { useIsDrawerOpen } from "#react-navigation/drawer";
let lastDrawerStateIsOpened = false;
const DrawerComponent = (props) => {
const isOpened = useIsDrawerOpen();
if (lastDrawerStateIsOpened != isOpened) {
lastDrawerStateIsOpened = isOpened;
if (isOpened) {
// Do what needs to be done when drawer is opened.
}
}
};
Also, note that I'm using a functional component.

React-Native: Can't output to the console.log()?

I'm in a weird state where I'm trying to bug track an issue and output to the console log and I'm not even getting that at this point.
The post is asking for more details but there aren't any. I'm just trying to figure out basic bug tracking at this point.
import React, { Component } from 'react';
import { View } from 'react-native';
import firebase from 'firebase';
import { Header, Button, CardSection, Spinner } from './components/common/';
import LoginForm from './components/LoginForm';
class App extends Component {
state = { loggedIn: null };
componentWillMount() {
firebase.initializeApp({
apiKey: 'xxxxxxxxxxxx',
authDomain: 'xxxxxxxxx.firebaseapp.com',
databaseURL: 'https://xxxxxxx.firebaseio.com',
projectId: 'xxxxx',
storageBucket: 'xxxxx.appspot.com',
messagingSenderId: 'xxxxxxxxx'
});
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ loggedIn: true });
} else {
this.setState({ loggedIn: false });
}
});
}
renderContent() {
if (this.state.loggedIn) {
switch (this.state.loggedIn) {
case true:
return console.log('true');
// return (
// <CardSection>
// <Button onPress={() => firebase.auth().signOut()}>
// Log Out
// </Button>
// </CardSection>
// );
case false:
return console.log('false');
// return <LoginForm />;
default:
return console.log('default');
// return <Spinner size="large" />;
}
}
}
render() {
return (
<View>
<Header headerText="Authentication" />
{this.renderContent()}
</View>
);
}
}
export default App;
The problem is that this.state.loggedIn is null the whole time, therefore your code enters never the switch statements. Accordingly you don't get an output.
I just figured it out. It's the IF statement that's right after the renderContent call; it's not supposed to be there. Sorry to bother.

Can this onPress, called in TochableOpacity, return a Text component?

[
the image is the resultof when you console.log(this.state.fixtures)
I havent run into an error, but also havent gotten a result. Just trying to pass the individual match, from the .map(), to the Card component. Not sure if the onPress should be called in the TouchableOpacity. Been looking at this so a couple of day, any feedback s appreciated. Thank You.
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, LayoutAnimation } from 'react-native';
import { Card, CardSection } from '../common';
//import ListOf from './ListOf';
export default class LeagueCard extends Component {
state ={
fixtures: null
}
componentDidMount = () => {
const {league_name, league_id } = this.props.league
{this.getMatches(league_id)}
}
getMatches = (league_id) => {
let legaueID = league_id
let fixArray = []
//console.log(legaueID)
fetch(`https://apifootball.com/api/?action=get_events&from=2016-10-30&to=2016-11-01&league_id=${legaueID}&APIkey=42f53c25607596901bc6726d6d83c3ebf7376068ff89181d25a1bba477149480`)
.then(res => res.json())
.then(fixtures => {
fixtures.map(function(fix, id){
fixArray =[...fixArray, fix]
})
this.setState({
fixtures: fixArray
})
console.log(this.state.fixtures)
})
}
display = () => {
//console.log(this.state.fixtures)
if(this.state.fixtures != null){
this.state.fixtures.map(function(match, id){
//console.log(match)
return (
<Text>match</Text>
)
})
}
}
render(){
const {league_name, league_id } = this.props.league
return(
<View>
<TouchableOpacity
onPress={() => this.display()}
>
<Card>
<CardSection>
<Text>{league_name}</Text>
</CardSection>
</Card>
</TouchableOpacity>
</View>
)}
}
enter code here
import React, {Component} from 'react';
import {View, Text, TouchableOpacity, LayoutAnimation} from 'react-native';
import {Card, CardSection} from '../common';
export default class LeagueCard extends Component {
constructor(props) {
super(props);
this.state = {
fixtures: null,
matches: []
}
}
componentDidMount() {
const {league_name, league_id} = this.props.league;
this.getMatches(league_id)
};
getMatches = (league_id) => {
let legaueID = league_id;
let fixArray = [];
fetch(`https://apifootball.com/api/?action=get_events&from=2016-10-30&to=2016-11-01&league_id=${legaueID}&APIkey=42f53c25607596901bc6726d6d83c3ebf7376068ff89181d25a1bba477149480`)
.then(res => res.json())
.then(fixtures => {
fixtures.map(function (fix, id) {
fixArray = [...fixArray, fix]
});
this.setState({
fixtures: fixArray
})
})
};
display = () => {
if (this.state.fixtures != null) {
this.setState({
matches: this.state.fixtures.map(match => <Text>{match.country_name}</Text>)
});
}
};
render() {
const {league_name, league_id} = this.props.league;
return (
<View>
<TouchableOpacity onPress={this.display}>
<Card>
<CardSection>
{this.state.matches}
</CardSection>
</Card>
</TouchableOpacity>
</View>
)
}
}
I made the changes I think will render your matches for you. I made your display() f set the state.matches of your LeagueCard, which will now be an array of Text components each displaying match. The Array.prototype.map function in JavaScript returns a new array which should then be used.
Also should mention that I added a constructor where I initialize state, though that is not strictly necessary it is a good practice.
Watch out for typos too, you have one in getMatches which I did not fix.
Edit: I changed match to match.country_name as you can't give objects directly to a Text component. You will need to grab each key/value pair of the object you want to display.
this will not show any error to you, but your components will not render, because react will not know where to render it, a workaround to this problem is try something like this
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, LayoutAnimation } from 'react-native';
import { Card, CardSection } from '../common';
//import ListOf from './ListOf';
export default class LeagueCard extends Component {
state ={
fixtures: null,
showTextList: false
}
componentDidMount = () => {
const {league_name, league_id } = this.props.league
{this.getMatches(league_id)}
}
getMatches = (league_id) => {
let legaueID = league_id
let fixArray = []
//console.log(legaueID)
fetch(`https://apifootball.com/api/?action=get_events&from=2016-10-30&to=2016-11-01&league_id=${legaueID}&APIkey=42f53c25607596901bc6726d6d83c3ebf7376068ff89181d25a1bba477149480`)
.then(res => res.json())
.then(fixtures => {
fixtures.map(function(fix, id){
fixArray =[...fixArray, fix]
})
this.setState({
fixtures: fixArray
})
})
}
display = () => {
//console.log(this.state.fixtures)
this.setState({showTextList: true})
}
render(){
const {league_name, league_id } = this.props.league
return(
<View>
<TouchableOpacity
onPress={() => this.display()}
>
<Card>
<CardSection>
<Text>{league_name}</Text>
{this.state.showTextList && this.state.fixtures &&
this.state.fixtures.map((match, id) => (<Text>{match}</Text>))
}
</CardSection>
</Card>
</TouchableOpacity>
</View>
)}
}
I just put the text list inside your CardSection, because i belive you want to render the list inside it,but feel free to put this wherewver you want

Click button not triggering "handleSubmit" function in React

I am building a react on rails app. I have a button on the page that user can indicate whether they want to join a meet up or not. Clicking "join" button should create a rsvp relation between the current user and an event, and the button will be switched to "Leave", if user then click on the "Leave" button, this relationship will be deleted from the rails backend. After messing around my react component, my "Join" button doesn't trigger the "onSubmit" function, and the "Leave" button seems to return an error saying "submission form cancelled because form is not connected". I'd appreciated a lot if any one can help me clean my logic.
import React from 'react'
class EventsIndexContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
active: props.rsvp
}
this.toggleButton = this.toggleButton.bind(this)
this.handleRsvpSubmit = this.handleRsvpSubmit.bind(this)
this.handleRsvpDelete = this.handleRsvpDelete.bind(this)
}
toggleButton() {
this.setState({active: !this.state.active})
}
handleRsvpSubmit(event) {
event.preventDefault()
let formPayLoad = {
user_id: this.props.current_user.id,
event_id: this.props.selectedId
}
this.props.addRsvp(formPayLoad)
}
handleRsvpDelete() {
fetch(`/api/v1/rsvps/${this.props.selectedId}`, {
method: 'DELETE'}
)
}
render() {
let button
let joinButton =
<form onSubmit={this.handleRsvpSubmit}>
<button type="button" onClick={() => (this.props.handleSelect(),
this.toggleButton())}>Join</button>
</form>
let leaveButton =
<button type="button" onClick={() => (this.toggleButton(),
this.handleRsvpDelete)}>Leave</button>
button = this.state.active? leaveButton : joinButton
return(
<div>
<h4>{this.props.location} - {this.props.meal_type} at
{this.props.time}</h4>
<p>{this.props.group.name}</p>
{button}
<button>See who is going</button>
</div>
)
}
}
export default EventsIndexContainer
This is the parent container:
import React from 'react'
import GroupIndexContainer from './GroupIndexContainer'
import EventsIndexContainer from './EventsIndexContainer'
class MainContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
groups: [],
current_user: null,
events: [],
rsvps: [],
selectedId: null
}
this.fetchGroups = this.fetchGroups.bind(this)
this.fetchEvents = this.fetchEvents.bind(this)
this.handleSelect = this.handleSelect.bind(this)
this.addRsvp = this.addRsvp.bind(this)
this.fetchRsvps = this.fetchRsvps.bind(this)
}
componentDidMount() {
fetch('api/v1/users.json', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState ({current_user: data.user})
})
.then(this.fetchGroups())
.then(this.fetchEvents())
.then(this.fetchRsvps())
}
fetchGroups() {
fetch('/api/v1/groups', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState({groups: data.groups})
})
}
fetchEvents() {
fetch('/api/v1/events', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState({events: data})
})
}
fetchRsvps() {
fetch('/api/v1/rsvps', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState({rsvps: data.rsvps})
})
}
handleSelect(id) {
this.setState({selectedId: id})
}
addRsvp(formPayLoad) {
fetch('/api/v1/rsvps', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json'},
body: JSON.stringify(formPayLoad)
})
}
render() {
let groups = this.state.groups.map((group) => {
return (
<GroupIndexContainer
key={group.id}
id={group.id}
name={group.name}
/>
)
})
let rsvp_ids = this.state.rsvps.map(rsvp => rsvp.event_id)
let events = this.state.events.map((event) => {
return(
<EventsIndexContainer
key={event.id}
id={event.id}
rsvp={rsvp_ids.some(rsvp_id => rsvp_id == event.id) ? true : false}
location={event.location}
meal_type={event.meal_type}
time={event.time}
group={event.group}
current_user={this.state.current_user}
user={event.user}
selectedId={this.state.selectedId}
addRsvp={this.addRsvp}
handleSelect={() => this.handleSelect(event.id)}
/>
)
})
return(
<div className="wrapper">
<div className="groups-index">
<h3>Your Groups:</h3>
{groups}
</div>
<div className="events-index">
<h3>What's happening today...</h3>
{events}
</div>
</div>
)
}
}
export default MainContainer
The button built before returning in the render function is either a form or a button. I would suggest to simply check the state of your component, avoid using the form (which is not inserted in the DOM, hence the "submission form cancelled because form is not connected" message).
Basically, your code will be much simpler if you use the onClick function of a button. You won't have to deal with the button types submit or button that will trigger onSubmit for the former or not for the latter as per : Difference between <input type='button' /> and <input type='submit' />
Also, using arrow functions in components properties is not a good practise, as well documented here : Why shouldn't JSX props use arrow functions or bind?
So I would suggest in a second time to change your onClick property to something like onClick={ this.handleLeave }, bind handleLeave in the constructor like you did for other functions, and handle the work there (and do the same for handleJoin).
I tried to rework a bit your code in the following snippet, hope this will help!
class EventsIndexContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
active: props.rsvp
}
this.toggleButton = this.toggleButton.bind(this)
this.handleRsvpSubmit = this.handleRsvpSubmit.bind(this)
this.handleRsvpDelete = this.handleRsvpDelete.bind(this)
// Stub
this.handleSelect = this.handleSelect.bind(this)
}
handleSelect() {
console.log("handleSelect called");
}
toggleButton() {
this.setState({active: !this.state.active})
}
// event argument removed here, wasn't used anyway
handleRsvpSubmit() {
console.log("handleRsvpSubmit called")
}
handleRsvpDelete() {
console.log("handleRsvpDelete called")
}
render() {
return(
<div>
<h4>Hello</h4>
<p>Group name</p>
{ this.state.active ?
<button type="button" onClick={() => (this.toggleButton(), this.handleRsvpDelete())}>Leave</button>
:
<button type="button" onClick={() =>(this.handleSelect(),this.toggleButton(), this.handleRsvpSubmit())}>Join</button>
}
<button>See who is going</button>
</div>
)
}
}
ReactDOM.render(
<EventsIndexContainer rsvp={ false } />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

Resources