componentDidUpdate not being called - react-native

I have a react native app, and I am calling componentDidUpdate on App.js, but it doesn't fire.
I wonder if this is because I am calling from App.js?
Here is the App.js files:
class App extends Component {
componentDidUpdate = () => {
if (this.props.text && this.props.text.toString().trim()) {
Alert.alert(this.props.title || 'Mensagem', this.props.text.toString());
this.props.clearMessage();
}
}
render() {
return (
<NavigationContainer>
<Navigator />
</NavigationContainer>
)
}
}
const mapStateToProps = ({ message }) => {
return {
title: message.title,
text: message.text
}
}
const mapDispatchToProps = dispatch => {
return {
clearMessage: () => dispatch(setMessage({
title: '',
text: ''
}))
}
}
const connectDispatch = connect(mapStateToProps, mapDispatchToProps);
const connectApp = connectDispatch(App);
export default connectApp;
And here is where I am calling it.Inside a dispatch in posts action.
.then(res => {
dispatch(fetchPosts());
dispatch(postCreated());
dispatch(setMessage({
title: 'Sucesso',
text: 'Nova Postagem!'
}));
});
All other dispatchs are fired.
It's not the if that is preventing the alert to be fired, because I already put the alert outside of the if.

Change this
componentDidUpdate = () => { ... }
for this:
componentDidUpdate(prevProps, prevState, snapshot) { ... }
Keep in mind the componentDidUpdate does not trigger on first render

Thanks all!
I could fix it.
Instead of importing from '.ActionTypes' I was importing from 'Message'
import { SET_MESSAGE } from '../actions/ActionTypes';
I am new to Redux and it caught me offguard!

Related

How to refresh data when navigating to the previous page in React Native?

What I'm Trying To Do
When navigating back from second page to first page, I want to refresh datas.
The problem is that when I navigate back from second page, onRefresh() in First doesn't work.
First Page
export default class First extends React.Component {
constructor(props) {
super(props);
this.state = {
refreshing: false,
items: [],
};
}
fetchData = async () => {
const querySnapshot = db.items();
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
this.setState({ items });
}
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
this.onRefresh;
}
onRefresh() {
this.setState({ refreshing: true });
this.fetchData().then(() => {
this.setState({ refreshing: false });
});
}
render() {
return (
<Container>
<Content
refreshControl={(
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.onRefresh.bind(this)}
/>
)}
>
</Content>
</Container>
);
}
}
Second Page
this.props.navigation.navigate('First', 'update');
I would appreciate it if you could give me any advices.
Suppose you have two pages PAGE-A & PAGE-B
create a JS-File named ForegroundBackground.js
import React from "react";
import { View } from "react-native";
const ForegroundBackground = ({ navigation, bgCallback, fgCallback }) => {
React.useEffect(() => navigation.addListener('focus', () => {
fgCallback && fgCallback()
}), []);
React.useEffect(() => navigation.addListener('blur', () => {
bgCallback && bgCallback()
}), []);
return (<View/>);
};
export default ForegroundBackground;
you can use it in render function of PAGE-A by providing navigation object to it from screen props.
<ForegroundBackground navigation = {this.props.navigation}
fgCallback = {()=>{alert("resume")}}/>
There are two solutions for the purpose.
To use store.Define a state in store and use that state in pageA and pageB.So if you change store state from pageB.It will auto reflect in entire app.
you can pass a function from pageA to pageB while navigation.The purpose of the function is to refresh state of PageA while moving back. For example:
this.props.navigation.navigate("pageB", {
resetData: () => {
this.setState({
mydata: ""
})
}
})
And while navigating from pageB you can do something like this:
this.props.navigation.state.params.resetData();
this.props.navigation.goBack();
I hope it helps. Leave a comment if you want to have more help/code/discussion etc

I am using redux in react native application to fetch and display data but its not updating on data change from backend

I am using Redux in my React-Native application.
I am fetching the data from api call and on success rendoring it on ListItem.
I am able to fetch and display data but data is not auto updating unless and until I revisit the page.
Even values are not storing into the app
I am calling method from actions in constructor and componentDidMount method.
Can you Please check the code and tell me where am I going wrong.
Here is action.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './types';
export const fetchingProductRequest = () => ({
type : FETCHING_PRODUCT_REQUEST
});
export const fetchingProductSuccess = (json) => ({
type : FETCHING_PRODUCT_SUCCESS,
payload : json
});
export const fetchingProductFailure = (error) => ({
type : FETCHING_PRODUCT_FAILURE,
payload : error
});
export const fetchProduct = () => {
return async dispatch => {
dispatch(fetchingProductRequest());
try {
let response = await fetch("http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop");
let json = await response.json();
dispatch(fetchingProductSuccess(json));
} catch(error) {
dispatch(fetchingProductFailure(error));
}
}
}
My reducer.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './../actions/types';
const initialState = {
loading : false,
errorMessage : '',
shops: []
}
const products = ( state = initialState, action ) => {
switch(action.type) {
case FETCHING_PRODUCT_REQUEST :
return { ...state, loading: true} ;
case FETCHING_PRODUCT_SUCCESS :
return { ...this.state, loading: false, shops: action.payload };
case FETCHING_PRODUCT_FAILURE :
return { ...state, loading: false, errorMessage: action.payload};
}
};
export default products;
product.js
import * as React from 'react';
import { FlatList , ActivityIndicator} from 'react-native';
import { ListItem } from 'react-native-elements';
import { fetchProduct } from './../../actions/products';
import { connect } from 'react-redux';
import propTypes from 'prop-types';
class Product extends React.Component {
constructor(props) {
super(props);
this.props.fetchProduct();
this.state = {
loading : true,
shops : '',
isFetching: false,
active : true,
}
}
fetchProducts() {
const shopid = 13;
fetch(`http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop`)
.then(response => response.json())
.then((responseJson)=> {
this.setState({
loading: false,
shops: responseJson
})
alert(JSON.stringify(this.state.shops));
})
.catch(error=>console.log(error)) //to catch the errors if any
}
componentDidMount(){
// this.fetchProducts();
this.props.fetchProduct().then(this.setState({loading : false}));
}
renderItem = ({ item }) => (
<ListItem
title={item.name}
subtitle={item.name}
leftAvatar={{
source: item.avatar && { uri: item.avatar },
title: item.name[0]
}}
bottomDivider
chevron
/>
)
render () {
if(!this.state.loading)
{
if(this.props.shopsInfo.loading)
{
return (
<ActivityIndicator/>
)
}
else
{
return (
<FlatList
vertical
showsVerticalScrollIndicator={false}
data={this.props.shopsInfo.shops}
renderItem={this.renderItem}
/>
)
}
}
else
{
return (
<ActivityIndicator/>
)
}
}
}
Product.propTypes = {
fetchProduct: propTypes.func.isRequired
};
const mapStateToProps = (state) => {
return { shopsInfo: state };
}
function mapDispatchToProps (dispatch) {
return {
fetchProduct: () => dispatch(fetchProduct())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Product);
1. Not updating on data change from backend.
You have to call an api on regular interval to get updated data. Redux implementation doesn't mean it will fetch data from server whenever there is any change.
2. Even values are not storing into the app
If you are expecting redux will store data even if you will close/kill an application than it will not. You have persist data in-order to use it or store it in cache. Take a look at redux-persist
The problem is your passing wrong props in mapStateToProps function.
In reducer your updating the response value in shop props.
In order to get the updated value you need to pass shops property to get the value.
const mapStateToProps = (state) => {
const { shops: state };
return {shops};
}

React-Native: Conditional App launch in wix/react-native-navigation - v1

I am using wix/react-native-navigation - v1 in my react native project and I want to launch my App based on a condition as follows:
Launch App
Read credentials from storage (AsyncStorage)
If credentials found, then
Start App with Home screen
Else
Start App with Login Screen
How can I achieve this?
I have index.js
import App from './App';
App.js
...
Navigation.registerComponent("myApp.AuthScreen", () => AuthScreen);
Navigation.registerComponent("myApp.HomeScreen", () => HomeScreen);
...
// Start a App
Navigation.startSingleScreenApp({
screen: {
screen: "myApp.AuthScreen",
title: "Login"
}
});
You can have two functions that initialize single-screen apps and then call the one that fulfills the requirements.
...
Navigation.registerComponent("myApp.AuthScreen", () => AuthScreen);
Navigation.registerComponent("myApp.HomeScreen", () => HomeScreen);
...
function startHomeScreen() {
Navigation.startSingleScreenApp({
screen: {
screen: "myApp.HomeScreen",
title: "Login"
}
});
}
function startAuthScreen() {
Navigation.startSingleScreenApp({
screen: {
screen: "myApp.AuthScreen",
title: "Home"
}
});
}
function init() {
if(...) {
startAuthScreen();
} else {
startHomeScreen();
}
}
It worked! I am not sure why the app kept hanging on splashscreen. Following is the exact code:
const __init__ = () => {
try {
AsyncStorage.getItem("MY-KEY")
.then((value) => {
if (value) {
startHomeScreen();
} else {
startAuthScreen();
}
});
} catch (e) {
startAuthScreen();
}
};
__init__();
Thanks #Filip Ilievski !
Navigation.registerComponent("RootScreen", () => RootScreen);
Navigation.startSingleScreenApp({
screen: {
screen: "RootScreen",
title: "Root"
}
});
For this scenarios you can create one additional component like below.
This additional component will check your condition in async storage and decide which view to load first
import AuthScreen from './AuthScreen';
import HomeScreen from './HomeScreen';
class RootScreen {
constructor(props) {
super(props);
this.state = {
loaded: false,
screenToLoad: ''
};
}
componentDidMount() {
this.checkRoute();
}
checkRoute = () => {
AsyncStorage.getItem("MY-KEY")
.then((value) => {
this.setState({
loaded: true,
screenToLoad: value
});
});
}
renderRoute = () => {
const { screenToLoad } = this.state;
switch(screenToLoad) {
case 'AuthScreen':
return <AuthScreen />;
case 'HomeScreen':
return <HomeScreen />
default:
return null;
}
}
render () {
const { loaded } = this.state;
if (!loaded) return null;
return this.renderRoute();
}
}

React native show a strange behavior. Can someone explain?

I'am creating a simple application with authentication. To change a state using redux with react-native-navigation (v1). For example, index.js
...
import { Navigation, } from 'react-native-navigation';
import { Provider, } from 'react-redux';
import store from './src/store';
import registerScreens from './src/screens';
registerScreens(store, Provider);
class App {
constructor () {
this.auth = false;
store.subscribe(this.onStoreUpdate.bind(this));
this.start();
}
onStoreUpdate () {
const state = store.getState();
if (this.auth != state.auth) {
this.auth = state.auth;
this.start();
}
}
start () {
switch (this.auth) {
case false:
Navigation.startTabBasedApp({
tabs: [{
screen: 'navigation.AuthScreen',
}, {
screen: 'navigation.RegisterScreen',
},],
});
break;
case true:
Navigation.startSingleScreenApp({
screen: {
screen: 'navigation.MainScreen',
},
});
break;
}
}
}
const application = new App();
Store is listening an update and change an application layout if need.
AuthScreen show a simple ActivityIndicator, when server request is perform. For example, auth.js
...
import { bindActionCreators, } from 'redux';
import { connect, } from 'react-redux';
import * as actions from './../actions';
...
class AuthScreen extends Component {
constructor (props) {
super(props);
this.state = {
loading: false,
...
};
this.handlePressEnter = this.handlePressEnter.bind(this);
}
handlePressEnter () {
...
this.loadingState(true);
jsonFetch(url, {
method: 'POST',
body: JSON.stringify({...}),
}).then((value) => {
this.loadingState();
this.props.actions.auth(true);
}).catch((errors) => {
this.loadingState();
console.log('Error while auth', errors);
});
}
...
loadingState (state = false) {
this.setState({
loading: state,
});
}
render () {
return (<View>
...
<Modal visible={this.state.loading} transparent={true} animationType="none" onRequestClose={() => {}}>
<View>
<ActivityIndicator size="large" animating={this.state.loading} />
</View>
</Modal>
</View>);
}
}
function mapStateToProps (state, ownProps) {
return {};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps) (AuthScreen);
I'am starting application with iOS simulator and try to authenticate. It show me activity indicator, then indicator disappear, but layout does not change. And strange behavior, if I comment this.loadingState(true); and this.loadingState(); in auth.js layout changes with success.
Can someone explain to me, why layout does not change from auth to main when activity indicator using?
I think that you can use dispatch props for loading.
For example When you call this.props.actions.auth(true);
You can return loading reducers.
handlePressEnter () {
...
dispatch({ type:'loading', loading: true });
jsonFetch(url, {
method: 'POST',
body: JSON.stringify({...}),
}).then((value) => {
dispatch({ type:'loading', loading: false });
this.props.actions.auth(true);
}).catch((errors) => {
this.loadingState();
console.log('Error while auth', errors);
});
}
And than you can use
<ActivityIndicator size="large" animating={this.props.loading} />
But dont forget the reducers return

Confirm/warn dialog on back

Like in the web browser, we have onBeforeUnload (vs onUnload) to show a alert or some warning "there is unsaved data - are you sure you want to go back".
I am trying to do the same. I couldn't find anything in the docs of react-navigation.
I thought of doing something real hacky like this, but I don't know if its the right way:
import React, { Component } from 'react'
import { StackNavigator } from 'react-navigation'
export default function ConfirmBackStackNavigator(routes, options) {
const StackNav = StackNavigator(routes, options);
return class ConfirmBackStackNavigatorComponent extends Component {
static router = StackNav.router;
render() {
const { state, goBack } = this.props.navigation;
const nav = {
...this.props.navigation,
goBack: () => {
showConfirmDialog()
.then(didConfirm => didConfirm && goBack(state.key))
}
};
return ( <StackNav navigation = {nav} /> );
}
}
}
React navigation 5.7 has added support for it:
function EditText({ navigation }) {
const [text, setText] = React.useState('');
const hasUnsavedChanges = Boolean(text);
React.useEffect(
() =>
navigation.addListener('beforeRemove', (e) => {
if (!hasUnsavedChanges) {
// If we don't have unsaved changes, then we don't need to do anything
return;
}
// Prevent default behavior of leaving the screen
e.preventDefault();
// Prompt the user before leaving the screen
Alert.alert(
'Discard changes?',
'You have unsaved changes. Are you sure to discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
// If the user confirmed, then we dispatch the action we blocked earlier
// This will continue the action that had triggered the removal of the screen
onPress: () => navigation.dispatch(e.data.action),
},
]
);
}),
[navigation, hasUnsavedChanges]
);
return (
<TextInput
value={text}
placeholder="Type something…"
onChangeText={setText}
/>
);
}
Doc: https://reactnavigation.org/docs/preventing-going-back
On current screen set
this.props.navigation.setParams({
needUserConfirmation: true,
});
In your Stack
const defaultGetStateForAction = Stack.router.getStateForAction;
Stack.router.getStateForAction = (action, state) => {
if (state) {
const { routes, index } = state;
const route = get(routes, index);
const needUserConfirmation = get(route.params, 'needUserConfirmation');
if (
needUserConfirmation &&
['Navigation/BACK', 'Navigation/NAVIGATE'].includes(action.type)
) {
Alert.alert('', "there is unsaved data - are you sure you want to go back", [
{
text: 'Close',
onPress: () => {},
},
{
text: 'Confirm',
onPress: () => {
delete route.params.needUserConfirmation;
state.routes.splice(index, 1, route);
NavigationService.dispatch(action);
},
},
]);
// Returning null from getStateForAction means that the action
// has been handled/blocked, but there is not a new state
return null;
}
}
return defaultGetStateForAction(action, state);
};
Notes,
Navigating without the navigation prop
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
NavigationService.js
function dispatch(...args) {
_navigator.dispatch(...args);
}
This can be accomplished by displaying a custom back button in the header, and capturing the hardware back-event before it bubbles up to the navigator.
We'll first configure our page to show a custom back button by overriding the navigation options:
import React, { Component } from 'react'
import { Button } from 'react-native'
function showConfirmDialog (onConfirmed) { /* ... */ }
class MyPage extends Component {
static navigationOptions ({ navigation }) {
const back = <Button title='Back' onPress={() => showConfirmDialog(() => navigation.goBack())} />
return { headerLeft: back }
}
// ...
}
The next step is to override the hardware back button. For that we'll use the package react-navigation-backhandler:
// ...
import { AndroidBackHandler } from 'react-navigation-backhandler'
class MyPage extends Component {
// ...
onHardwareBackButton = () => {
showConfirmDialog(() => this.props.navigation.goBack())
return true
}
render () {
return (
<AndroidBackHandler onBackPress={this.onHardwareBackButton}>
{/* ... */}
</AndroidBackHandler>
)
}
}