I have just started integrating Redux into my first React Native (Expo.io) project. I have got login working great with Redux, but when I try and create a logout button on one of the screens in my app, it actually triggers the log out dispatch as soon as it loads it. I think I must be misunderstanding the way Redux connect and mapDispatchToProps work. I have read the documentation many times but am still stuck. Here is the code in it's non-working state.
Log In - Works until I add the log out dispatch on profile page
import { connect } from "react-redux";
import React, { Component } from "react";
import {
Button,
View,
Text,
ActivityIndicator,
Alert,
FlatList
} from "react-native";
import { NavigationActions } from "react-navigation";
import { SocialIcon, Card } from "react-native-elements";
import Reactotron from "reactotron-react-native";
import { logIn } from "../actions";
import { signIn } from "../components/Auth";
class SignIn extends Component {
async handleClick() {
res = await signIn();
if (res != false) {
this.props.logIn(res.token);
} else {
console.log("Login Failed");
}
}
render() {
return (
<View style={{ paddingVertical: 20 }}>
<Card title="finis Requires A Facebook Account To Operate">
<SocialIcon
title="Fred"
button
type="facebook"
onPress={() => this.handleClick()}
/>
</Card>
</View>
);
}
}
const mapDispatchToProps = dispatch => {
return {
logIn: fbToken => {
dispatch(logIn(fbToken));
}
};
};
LoginScreen = connect(null, mapDispatchToProps)(SignIn);
export default LoginScreen;
Reducers
import { combineReducers } from "redux";
import Reactotron from "reactotron-react-native";
import { LOG_IN, LOG_OUT, ADD_PHONE_CONTACTS } from "../actions/actions";
const initialState = {
signedIn: false,
fbToken: "fred",
test: undefined,
phoneContacts: {}
};
const finis = combineReducers({
auth,
phoneContacts
});
function auth(state = initialState, action) {
switch (action.type) {
case LOG_IN:
Reactotron.log("LOG IN");
return {
...state,
signedIn: true,
fbToken: action.fbToken
};
case LOG_OUT:
Reactotron.log("LOG OUT");
return {
...state,
signedIn: false,
fbToken: undefined
};
default:
return state;
}
}
function phoneContacts(state = [], action) {
switch (action.type) {
case ADD_PHONE_CONTACTS:
console.log("Adding Contacts");
return {
...state,
phoneContacts: action.phoneContacts
};
default:
return state;
}
}
export default finis;
Profile Non working. Triggers LOG_OUT action without the button being pushed.
import React, { Component } from "react";
import { Button, Card } from "react-native-elements";
import { View, Text, ActivityIndicator, AsyncStorage } from "react-native";
import { MapView } from "expo";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { SimpleLineIcons } from "#expo/vector-icons";
import Reactotron from "reactotron-react-native";
import * as ActionCreators from "../actions";
import { signOut } from "../components/Auth";
class ProfileWrap extends Component {
handleClick() {
Reactotron.log(this.Actions);
this.props.logOut();
}
render() {
return (
<View style={{ paddingVertical: 20 }}>
<Card title="Profile">
<View
style={{
backgroundColor: "#bcbec1",
alignItems: "center",
justifyContent: "center",
width: 80,
height: 80,
borderRadius: 40,
alignSelf: "center",
marginBottom: 20
}}
>
<Text style={{ color: "white", fontSize: 28 }}>JD</Text>
</View>
<Button title="Log Out" onPress={this.handleClick} />
</Card>
</View>
);
}
}
mapDispatchToProps = dispatch => {
return {
logOut: dispatch(logOut())
};
};
const Profile = connect(null, mapDispatchToProps)(ProfileWrap);
export default Profile;
Any help would be appreciated, even if it's telling me I'm doing the whole thing wrong :) Been at this for hours.
NEW Profile.js - Gives Cannot read property 'logOut' of undefined
import React, { Component } from "react";
import { Button, Card } from "react-native-elements";
import { View, Text, ActivityIndicator, AsyncStorage } from "react-native";
import { MapView } from "expo";
import { connect } from "react-redux";
import { SimpleLineIcons } from "#expo/vector-icons";
import { logOut } from "../actions";
import { signOut } from "../components/Auth";
class ProfileWrap extends Component {
handleClick() {
console.log(this.props);
this.props.logOut();
}
render() {
return (
<View style={{ paddingVertical: 20 }}>
<Card title="Profile">
<View
style={{
backgroundColor: "#bcbec1",
alignItems: "center",
justifyContent: "center",
width: 80,
height: 80,
borderRadius: 40,
alignSelf: "center",
marginBottom: 20
}}
>
<Text style={{ color: "white", fontSize: 28 }}>JD</Text>
</View>
<Button title="Log Out" onPress={this.handleClick} />
</Card>
</View>
);
}
}
const mapDispatchToProps = dispatch => {
return {
logOut: function() {
dispatch(logOut());
}
};
};
const Profile = connect(null, mapDispatchToProps)(ProfileWrap);
export default Profile;
Your mapDispatchToProps should be returning an object with functions. The way you have it now, logOut() will be called immediately since it's not inside a function. Changing it to this should fix it:
const mapDispatchToProps = dispatch => {
return {
logOut: function () {
dispatch(logOut());
}
};
};
Here's a slightly cleaner way of doing it:
const mapDispatchToProps = dispatch => ({
logOut() {
dispatch(logOut());
}
});
Also, you're missing const in front of mapDispatchToProps but that shouldn't have affected anything.
Edit:
You don't have to use this now, but it'll be helpful in the future - if your component is only using the render method you can change it to a stateless functional component. It's currently the recommended way of creating components when possible:
const ProfileWrap = props => (
<View style={{ paddingVertical: 20 }}>
<Card title="Profile">
<View
style={{
backgroundColor: "#bcbec1",
alignItems: "center",
justifyContent: "center",
width: 80,
height: 80,
borderRadius: 40,
alignSelf: "center",
marginBottom: 20
}}
>
<Text style={{ color: "white", fontSize: 28 }}>JD</Text>
</View>
<Button title="Log Out" onPress={props.logOut} />
</Card>
</View>
);
Related
My app is not loading after splash screen. It just stuck there.
So I added expo-splash-screen and still is not passing the splash screen. Before adding splash screen everything was working fine :(
See this is my App.js code. As you can see it only holds the navigation container which holds the links to other screens including the main home screen.
import {StatusBar } from 'expo-status-bar';
import { StyleSheet } from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { MaterialIcons } from "#expo/vector-icons";
import { HomeNavigator } from './CustomNavigation';
import * as Font from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import FavouritesScreen from './src/screens/FavouritesScreen'
import HomeScreen from './src/screens/HomeScreen';
import MoreOptions from './src/screens/MoreOptions'
import React, { useEffect, useState } from 'react'
console.reportErrorsAsExceptions = false; //to hide touch warning
const Stack = createNativeStackNavigator()
const Tab = createBottomTabNavigator();
SplashScreen.preventAutoHideAsync();
export default function App() {
const [fontLoaded, setFontLoaded] = useState(false)
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
await Font.loadAsync(Entypo.font);
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;` `
}
return (
<View
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}
onLayout={onLayoutRootView}>
<StatusBar barStyle="dark-content" hidden={false} backgroundColor="#ff3b3b" translucent={true} />
<NavigationContainer>
//it contains nothing but the navigation code
</NavigationContainer>
</View>)
Please tell me where and what I'm doing wrong here.
I give you the working example please check it
then after you don't understand let me know
code
import React, { useRef, useEffect } from 'react';
import { StyleSheet, View, Image, StatusBar, Text } from 'react-native';
/**
* RN libraries
*/
import { useNavigation } from '#react-navigation/native';
export default function Splash() {
const animation = useRef(null);
const navigation = useNavigation();
useEffect(() => {
animation.current?.play();
navigation.addListener('focus', () => {
animation.current?.play();
});
setTimeout(() => {
navigate();
}, 2500);
}, []);
const navigate = () => {
navigation.navigate('Home');
};
return (
<View style={styles.animationContainer}>
<Text
style={{
textAlign: 'center',
fontSize: 50,
fontStyle: 'italic',
fontFamily: 'Poppins-Medium',
fontWeight: '800',
color: '#fff',
}}>
Online{'\n'}Education
</Text>
<Text
style={{ color: '#fff', fontSize: 20, fontFamily: 'Poppins-Medium' }}>
Free{'\n'}
</Text>
<View
style={{
backgroundColor: '#5D6CFA',
width: 169,
height: 117,
borderRadius: 60,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
}}>
{/* <Text style={{color:"#000",fontFamily:'Poppins-Bold',fontSize:25}}>Let's start</Text> */}
</View>
</View>
);
}
const styles = StyleSheet.create({
animationContainer: {
backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
},
});
Maybe pass your setAppIsReady(true); after the finally and then remove it like this
React.useEffect(() => {
async function prepare() {
try {
// Pre-load fonts, make any API calls you need to do here
AsyncStorage.setItem("alreadyAppLaunched", "true")
await LoadFonts()
await checkOsDarkMode()
await stores.AuthStore.getAllData()
} catch (e) {
console.warn(e);
}
setReady(true);
}
prepare()
}, [])
Trying to implement redux into one of my existing apps (for the first time). Essentially trying to do something quite simple, use a text input to store a string in the store and relay it back in another component using redux. However although the action dispatches, when I log it to the console, I keep getting 'undefined' for the entered string. Not sure where I'm going wrong and other questions aren't making it much clearer for my beginners mind!
I've defined singular reducers, combined them in an index, created the store (passing my combined reducer), wrapped navigation (entire app) in a provider with my store, created a dispatch and event/action to activate the dispatch (basically when the user enters a character and they press a continue button).
textInputPost.js: (Reducer)
const initialState = {
textInputValue: '',
};
const textInputReducer = (state = initialState, action) => {
console.log('textInputReducer', action);
switch(action.type){
case 'ADD_INPUT_TEXT':
return { textInputValue: action.text };
case 'RESET_INPUT_TEXT':
return { textInputValue: ''}
default:
return state;
}
}
export default textInputReducer;
index.js: (rootReducer - combine reducers)
import { combineReducers } from 'redux';
import photoInputPost from './photoInputPost'
import cameraInputPost from './cameraInputPost'
import textInputPost from './textInputPost'
export default rootReducer = combineReducers({
text: textInputPost
})
Index.js: (Store)
import { createStore } from 'redux'
import rootReducer from './../reducers/index'
export default Store = createStore(rootReducer)
App.js: (Provider wrapped around React Navigation)
return (
<Provider store={Store}>
<Navigation/>
</Provider>
);
AddTextModal.js: (Component for updating store state on textInput)
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, KeyboardAvoidingView, TextInput } from 'react-native';
import { AntDesign } from '#expo/vector-icons';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { FontAwesome5, Feather } from "#expo/vector-icons";
import { connect } from 'react-redux'
import ModalContainer from '../../App'
class AddTextModal extends React.Component {
continueUpload = (textInput) => {
this.props.addText(textInput)
console.log(this.props.textInputValue)
this.props.navigation.navigate('Upload')
}
render() {
return(
<View style={{backgroundColor:"#000000CC", flex:1}}>
<View style={{ backgroundColor:"#ffffff", marginLeft: 0, marginRight: 0, marginTop: 180, padding: 20, borderTopLeftRadius: 20, borderTopRightRadius: 20, flex: 1, }}>
<View style={styles.header}>
<TouchableOpacity style={{position: 'absolute'}} onPress={() => this.props.navigation.navigate('Home')}>
<Text style={styles.buttonFont}>Back</Text>
</TouchableOpacity>
<Text style={styles.headerText}>Write Something</Text>
<TouchableOpacity style={{position: 'absolute', right: 0}} onPress={(textInput) => this.continueUpload(textInput)}>
<Text style={styles.buttonFont}>Continue</Text>
</TouchableOpacity>
</View>
<View style={styles.uploadTextInput}>
<Feather name="message-square" size={14} color="grey" />
<TextInput style={{paddingLeft: 5, fontSize: 14}}
placeholder="What do you want to say?"
defaultValue={this.props.textInputValue}
onChangeText={textInput => this.props.addText(textInput)}
/>
</View>
</View>
</View>
);
}
}
//#6 mapStateToProps to access store from our component
function mapStateToProps(state){
return {
textInputValue: state.textInputValue
}
}
//#10. matchDispatchertoProps to establish dispatcher for actions. These actions will then go to functions in the reducer to change the app state
function mapDispatchToProps(dispatch) {
return {
addText: () => dispatch({type: 'ADD_INPUT_TEXT'}),
}
}
UploadScreen.js: (Component for relaying store state of text Input of AddTextModal)
import React from 'react';
import { Text, Image, View, TextInput, TouchableHighlight, TouchableOpacity } from 'react-native';
import { FontAwesome5, Feather } from "#expo/vector-icons";
import { connect } from 'react-redux'
import ToolbarComponent from './ToolbarComponent'
import styles from './../Styles';
import textInput from './../../containers/textInput'
class UploadScreen extends React.Component {
uploadMedia = () => {
//upload to DB - add to vault, home screen
this.props.resetText()
this.props.navigation.navigate('Home')
}
//viewPhoto function for viewing photo on full screen
viewPhoto = () => {
return
}
render() {
return(
<View style={{backgroundColor:"#000000CC", flex:1}}>
<View style={{ backgroundColor:"#ffffff", marginLeft: 0, marginRight: 0, marginTop: 180, padding: 20, borderTopLeftRadius: 20, borderTopRightRadius: 20, flex: 1, }}>
<View style={styles.header}>
<TouchableOpacity style={{position: 'absolute'}} onPress={() => this.props.navigation.goBack()}>
<Text style={styles.buttonFont}>Back</Text>
</TouchableOpacity>
<TouchableOpacity style={{position: 'absolute', right: 10}} onPress={() => this.uploadMedia()}>
<Text style={styles.buttonFont}>Upload</Text>
</TouchableOpacity>
<View style={styles.uploadTextInput}>
<Text>{this.props.textInputValue}</Text>
</View>
</View>
</View>
</View>
);
}
}
function mapStateToProps(state){
return {
textInputValue: state.textInputValue
}
}
function mapDispatchToProps(dispatch) {
return {
resetText:() => dispatch({type: 'RESET_INPUT_TEXT'})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UploadScreen);
I believe your issue is that you haven't specified a payload in your mapDispatchToProps function. Try re-writing your mapDispatchToProps function to look something like:
function mapDispatchToProps(dispatch) {
return {
addText: (payload) => dispatch({type: 'ADD_INPUT_TEXT', payload: payload}),
}
}
And since I renamed your payload, 'payload', change 'action.text' to 'action.payload' in your reducer:
const initialState = {
textInputValue: '',
};
const textInputReducer = (state = initialState, action) => {
console.log('textInputReducer', action);
switch(action.type){
case 'ADD_INPUT_TEXT':
return { textInputValue: action.payload };
case 'RESET_INPUT_TEXT':
return { textInputValue: ''}
default:
return state;
}
}
export default textInputReducer;
I recently integrated React Redux and Redux Thunk into my application in the hope that it would better allow me to manage state across screens.
However, using my navigation library (react native router flux), when ever I navigate between screens I get warnings of trying to set state across unmounted components and I am not sure what I would even need to unmount in componentWillUnmount as no calls should happen after a screen navigation.
My question then is, how can I force unmount everything on componentWillUnmount? Is there something built into React Native that I should use? Or, in my navigation library?
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import * as Font from 'expo-font'
class CustomText extends Component {
async componentDidMount() {
await Font.loadAsync({
'varelaround-regular': require('../../../assets/fonts/varelaround-regular.ttf'),
'opensans-regular': require('../../../assets/fonts/opensans-regular.ttf'),
'opensans-bold': require('../../../assets/fonts/opensans-bold.ttf'),
});
this.setState({ fontLoaded: true });
}
state = {
fontLoaded: false,
};
setFontType = type => {
switch (type) {
case 'header':
return 'varelaround-regular';
case 'bold':
return 'opensans-bold';
default:
return 'opensans-regular';
}
};
render() {
const font = this.setFontType(this.props.type ? this.props.type : 'normal');
const style = [{ fontFamily: font }, this.props.style || {}];
const allProps = Object.assign({}, this.props, { style: style });
return (
<View>
{
this.state.fontLoaded ? (
<Text {...allProps}>{this.props.children}</Text>
) : <Text></Text>
}
</View>
);
}
}
export default CustomText;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
And one of my screens:
import React from "react";
import { ActivityIndicator, Image, StyleSheet, View } from "react-native";
import { Actions } from "react-native-router-flux";
import { connect } from "react-redux";
import * as profile from "../actions/profile";
import {
CustomText
} from "../components/common/";
class Home extends React.Component {
componentDidMount() {
this.props.loadProfile();
}
renderScreen() {
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 0.3 }}>
<CustomText type="header" style={styles.headerTextStyle} onPress={() => Actions.home()}>
Hello {this.props.name}!
</CustomText>
</View>
</View>
);
}
renderWaiting() {
return (
<GradientBackground type="purple">
<View
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
>
<ActivityIndicator size="large" color="#FFF" />
</View>
</GradientBackground>
);
}
render() {
return (
<View style={{ flex: 1 }}>
{this.props.isLoading == true
? this.renderWaiting()
: this.renderScreen()}
</View>
);
}
}
function mapStateToProps(state) {
return {
name: state.profile.profile.friendly_name,
isLoading: state.profile.isLoading,
error: state.profile.error
};
}
function mapDispatchToProps(dispatch) {
return {
loadProfile: () => dispatch(profile.loadProfile())
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Home);
const styles = StyleSheet.create({
headerTextStyle: {
color: "#FFFFFF",
fontSize: 40,
textAlign: "center",
marginVertical: 50
},
basicViewStyle: {
flex: 1
}
});
I want the state of my variable (with which it is given a value from a textInput) is sent to a reducer and change the state of that reducer by the state of the variable that I sent, so that way I can show it in different screens using mapStateToProps and I get it globally.
Is there any way to do that? I researched and found examples but not the way I want to do it.
I clarify my code is just an example so that you understand what I want to do, do not take it as a guide as I do not know if it works that way
I show you some of my code to give you an idea of what I
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity
} from "react-native";
import { connect } from 'react-redux';
class ScreenHome extends Component {
static navigationOptions = {
header: null
}
constructor(props) {
super(props);
this.state = {
Texto: '',
}
}
render() {
this.props.ChangeState({type: 'ACTION_TYPE', Texto: this.state.Texto});
const { navigation } = this.props;
return (
<View style={styles.container}>
<TextInput
placeholder="Enter Text"
value={this.state.Texto}
onChangeText={Texto => this.setState({ Texto })}
/>
<View style={{ marginBottom: 10, marginTop: 10, backgroundColor: 'black', padding: 10 }}>
<TouchableOpacity onPress={this.props.ChangeState}>
<Text style={{ color: 'white' }}>Send Text Input status to the reducer</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity style={{ backgroundColor: 'blue', padding: 10 }} onPress={() => { navigation.navigate('Other') }}>
<Text style={{ color: '#fff' }}>Go</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
// prop: state.prop
}
}
const mapDispatchToProps = (dispatch) => {
return {
ChangeState: () => {
// dispatch({ type: 'ACTION_TYPE', Texto: this.state.Texto });
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenHome)
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
OTHER SCREEN:
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity
} from "react-native";
import { connect } from 'react-redux';
class ScreenOther extends Component {
static navigationOptions = {
header: null
}
render() {
const { navigation } = this.props;
return (
<View style={styles.container}>
<Text>{this.props.StateInitial}</Text>
<TouchableOpacity onPress={() => { navigation.navigate('Home') }}>
<Text>Go back</Text>
</TouchableOpacity>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
StateInitial: state.reducerText
}
}
const mapDispatchToProps = (dispatch) => {
return {
// ChangeState: () => {
// dispatch({type: 'CHANGE_TEXT'})
// }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenOther)
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
Store
import { createStore, combineReducers } from 'redux';
const reducerText = (state = [0], action) => {
switch (action.type) {
case 'ACTION_TYPE':
return {...state, Texto:action.Texto};
default:
return state;
}
};
const Reducers = combineReducers({
reducerText
})
const Store = createStore(Reducers)
export default Store;
dispatch1 should be visible in your props in the homescreen. So if you do
this.props.dispatch1({type: 'YOUR_ACTION_TYPE', Text: this.state.Text});
Your reducer function will be called where you can do:
reducer: (state, action) => {
switch (action.type) {
case 'YOUR_ACTION_TYPE':
return {...state, Text:action.Text};
}
}
Then in the other screen you should get the changed Text prop.
For those who look at this post and want to do something similar, I mean send the status of the textInput variable to a reducer and ask for the status from another screen with redux feel free to see the code that I will leave below since I was investigating and I got it after a while.
This is the code of redux-form
import React, { Component } from "react";
import {
View,
TextInput,
StyleSheet,
Button,
Text
} from "react-native";
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
const ScreenFormHome = (props) => (
<View>
<Field name="Text" component={fieldNombre} ph="Enter Text" />
<Button title="Send Dispatch" onPress={props.handleSubmit((values) => props.SendDispatch(values))} />
</View>
);
const fieldNombre = (props) => (
<View style={styles.textInput}>
<TextInput
placeholder={props.ph}
onChangeText={props.input.onChange}
value={props.input.value}
autoCapitalize="none"
onBlur={props.input.onBlur}
/>
<View style={styles.linea} />
</View>
);
const styles = StyleSheet.create({
textInput: {
marginBottom: 16,
},
linea: {
backgroundColor: '#DCDCDC',
height: 2,
},
});
const mapDispatchToProps = (dispatch) => {
return {
SendDispatch: (values) => {
dispatch({ type: 'ACTION_TYPE', Text: values.Text })
}
}
}
const mapStateToProps = (state) => {
return {
// StateInitial: state.reducerText
}
}
export default reduxForm({
form: 'ScreenFormHome',
})(connect(mapStateToProps, mapDispatchToProps)(ScreenFormHome));
and this is the component code
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity
} from "react-native";
import ScreenFormHome from "./ScreenFormHome";
class ScreenHome extends Component {
static navigationOptions = {
header: null
}
render() {
const { navigation } = this.props;
return (
<View style={styles.container}>
<TouchableOpacity style={{ backgroundColor: 'blue', padding: 10, marginBottom: 10 }} onPress={() => { navigation.navigate('Other') }}>
<Text style={{ color: '#fff' }}>Go</Text>
</TouchableOpacity>
<ScreenFormHome />
</View>
);
}
}
export default ScreenHome;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
This is the Store code
import { createStore, combineReducers } from 'redux';
import { reducer as form } from 'redux-form'
const reducerText = (state = [], action) => {
switch (action.type) {
case 'ACTION_TYPE':
return (action.Text)
default:
return state;
}
};
const Reducers = combineReducers({
reducerText,
form
})
const Store = createStore(Reducers)
export default Store;
So I have this problem where a variable update is slower than a route change.
When I have an error on for example my registration view, the error shows up instantly. When press back to come back to the login view, the error is being reset (action "clearErrors" is being fired on componentWillUnmount) to an empty string via an action. The problem is that I can se the error message on the login for a brief moment before it receives the new empty error state.
ErrorReducer.js
import {
ERROR,
CLR_ERROR
} from '../actions/types';
const INIT_STATE = {
error: ''
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case ERROR:
return { ...state, error: action.payload };
case CLR_ERROR:
return { ...state, error: '' };
default:
return state;
}
};
error.js (actions)
import { CLR_ERROR } from './types';
export const clearErrors = () => {
return (dispatch) => {
dispatch({ type: CLR_ERROR });
};
};
LoginForm.js
import React, { Component } from 'react';
import { View } from 'react-native';
import { Actions } from 'react-native-router-flux';
import { connect } from 'react-redux';
import { emailChanged, passwordChanged, loginUser, resetRoute, autoLogin } from '../actions';
import { Button, Input, Message } from './common';
class LoginForm extends Component {
componentWillUnmount() {
this.props.resetRoute();
}
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
this.props.loading = true;
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
render() {
return (
<View
style={{
flex: 1,
marginLeft: 10,
marginRight: 10,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}}
keyboardShouldPersistTaps="always"
keyboardDismissMode="on-drag"
>
<Message
type="danger"
message={this.props.error}
/>
<Input
placeholder="din#email.se"
keyboardType="email-address"
returnKeyType="next"
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
icon="ios-mail"
/>
<Input
secureTextEntry
placeholder="ditt lösenord"
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
icon="ios-key"
iconSize={22}
/>
<Button
loading={this.props.loading}
uppercase
color="primary"
label="Logga in"
onPress={this.onButtonPress.bind(this)}
/>
<Button
fontColor="primary"
label="Registrera"
onPress={() => Actions.register()}
/>
<Button
fontColor="primary"
label="Glömt lösenord"
onPress={() => Actions.resetpw()}
/>
</View>
);
}
}
const mapStateToProps = ({ auth, errors }) => {
const { email, password, loading, token } = auth;
const { error } = errors;
return { email, password, error, loading, token };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, loginUser, resetRoute, autoLogin
})(LoginForm);
Message.js (component to show error)
import React from 'react';
import { View, Text } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { colors } from '../style';
export const Message = ({ type, message }) => {
const style = {
view: {
alignSelf: 'stretch',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
margin: 15,
backgroundColor: colors[type],
borderRadius: 3,
elevation: 5,
shadowRadius: 5,
shadowColor: colors.smoothBlack,
shadowOffset: { width: 2.5, height: 2.5 },
shadowOpacity: 0.5
},
text: {
color: colors.alternative,
fontSize: 12,
alignSelf: 'center',
flex: 1
},
icon: {
marginRight: 20,
marginLeft: 0,
marginTop: 2,
alignSelf: 'center'
}
};
const getIcon = (iconType) => {
switch (iconType) {
case 'info':
return 'ios-information-circle';
case 'success':
return 'ios-checkmark-circle';
case 'danger':
return 'ios-alert';
case 'warning':
return 'ios-warning';
default:
return;
}
};
if (message.length > 0) {
return (
<View style={style.view}>
{(type) ? <Icon name={getIcon(type)} size={20} style={style.icon} /> : null}
<Text style={style.text}>{message}</Text>
</View>
);
}
return <View />;
};
I am running on device OnePlus3 with all console.logs removed, production build.
From what I have read, this should be fast. Am I doing something wrong here?
It's impossible to say without looking at your rendering code, but it's likely that the slowness is not caused by the time it takes for redux to update the state, but for React to re-render the UI after the dispatch has completed - possibly because it's busy re-rendering other things while your navigator is transitioning.
To guarantee the ordering of actions with redux-thunk, you can return a Promise from your thunk action creator and wait to navigate back until the action has been dispatched:
export const clearErrors = () => {
return (dispatch) => {
return new Promise(dispatch({ type: CLR_ERROR }));
};
};
In your view then, you can do the back navigation action once the error has been cleared:
// assuming the action creator has been passed
// to the component as props
this.props.clearErrors().then(() => navigator.back());