Stop MapStateToProps executing on screen load - react-native

I have created a register page and I am trying hook up a loading ticker while I create the user account.
I am running into an issue where mapStateToProps() is being executed whenever the screen loads meaning that any values I have being mapped error as the state is undefined. None of my reducers or actions are executing to cause mapStateToProps() to run. Have I set something within my screen to cause this to execute, I completely understand that my state is indeed undefined but why does mapStateToProps even run in the initial load?
.......
interface State {
name: string,
email: string;
mobileNo: string;
password: string;
passwordConf: string;
nameTouched: boolean;
emailTouched: boolean;
mobileNoTouched: boolean;
passwordTouched: boolean;
passwordConfTouched: boolean;
loading: boolean;
}
class RegisterScreen extends React.Component<{
navigation: any;
register: Function}, State> {
emailInputRef = React.createRef<FormTextInput>();
mobileNoInputRef = React.createRef<FormTextInput>();
passwordInputRef = React.createRef<FormTextInput>();
passwordConfInputRef = React.createRef<FormTextInput>();
readonly state: State = {
name: "",
email: "",
password: "",
mobileNo:"",
passwordConf: "",
emailTouched: false,
passwordTouched: false,
nameTouched: false,
mobileNoTouched: false,
passwordConfTouched: false,
loading: false
};
handleNameChange = (name: string) => {
this.setState({ name: name });
};
handleEmailChange = (email: string) => {
this.setState({ email: email });
};
handleMobileNoChange = (mobileNo: string) => {
this.setState({ mobileNo: mobileNo });
};
handlePasswordChange = (password: string) => {
this.setState({ password: password });
};
handlePasswordConfChange = (passwordConf: string) => {
this.setState({ passwordConf: passwordConf });
};
handleNameSubmitPress = () => {
if (this.emailInputRef.current) {
this.emailInputRef.current.focus();
}
};
handleEmailSubmitPress = () => {
if (this.mobileNoInputRef.current) {
this.mobileNoInputRef.current.focus();
}
};
handleMobileNoSubmitPress = () => {
if (this.passwordInputRef.current) {
this.passwordInputRef.current.focus();
}
};
handlePasswordSubmitPress = () => {
if (this.passwordConfInputRef.current) {
this.passwordConfInputRef.current.focus();
}
};
handleNameBlur = () => {
this.setState({ nameTouched: true });
};
handleEmailBlur = () => {
this.setState({ emailTouched: true });
};
handleMobileNoBlur = () => {
this.setState({ mobileNoTouched: true });
};
handlePasswordBlur = () => {
this.setState({ passwordTouched: true });
};
handlePasswordConfBlur = () => {
this.setState({ passwordConfTouched: true });
};
render() {
const {
name,
email,
password,
mobileNo,
passwordConf,
emailTouched,
passwordTouched,
nameTouched,
mobileNoTouched,
passwordConfTouched,
} = this.state;
const nameError =
!name && nameTouched
? strings.NAME_REQUIRED
: undefined;
const emailError =
!email && emailTouched
? strings.EMAIL_REQUIRED
: undefined;
const mobileError =
!mobileNo && mobileNoTouched
? strings.MOBILE_REQUIRED
: undefined;
const passwordError =
!password && passwordTouched
? strings.PASSWORD_REQUIRED
: undefined;
const passwordConfError =
!passwordConf && passwordConfTouched && (password === passwordConf)
? strings.PASSWORD_CONF_REQUIRED
: undefined;
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
>
<Image source={imagePath} style={styles.logo} />
<View style={styles.form}>
{/* Name */}
<FormTextInput
keyboardType={"default"}
value={this.state.name}
onChangeText={this.handleNameChange}
onSubmitEditing={this.handleNameSubmitPress}
placeholder={strings.NAME_PLACEHOLDER}
autoCorrect={false}
returnKeyType="next"
onBlur={this.handleNameBlur}
error={nameError}
/>
{/* Email */}
<FormTextInput
keyboardType={"email-address"}
ref={this.emailInputRef}
value={this.state.email}
onChangeText={this.handleEmailChange}
onSubmitEditing={this.handleEmailSubmitPress}
placeholder={strings.EMAIL_PLACEHOLDER}
autoCorrect={false}
returnKeyType="next"
onBlur={this.handleEmailBlur}
error={emailError}
/>
{/* MobileNo */}
<FormTextInput
keyboardType={"numeric"}
ref={this.mobileNoInputRef}
value={this.state.mobileNo}
onChangeText={this.handleMobileNoChange}
onSubmitEditing={this.handleMobileNoSubmitPress}
placeholder={strings.MOBILE_PLACEHOLDER}
autoCorrect={false}
returnKeyType="next"
onBlur={this.handleMobileNoBlur}
error={mobileError}
/>
{/* Password */}
<FormTextInput
keyboardType={"default"}
ref={this.passwordInputRef}
value={this.state.password}
onChangeText={this.handlePasswordChange}
onSubmitEditing={this.handlePasswordSubmitPress}
placeholder={strings.PASSWORD_PLACEHOLDER}
secureTextEntry={true}
returnKeyType="done"
onBlur={this.handlePasswordBlur}
error={passwordError}
/>
{/* Password Conf */}
<FormTextInput
keyboardType={"default"}
ref={this.passwordConfInputRef}
value={this.state.passwordConf}
onChangeText={this.handlePasswordConfChange}
placeholder={strings.PASSWORD_CONF_PLACEHOLDER}
secureTextEntry={true}
returnKeyType="done"
onBlur={this.handlePasswordConfBlur}
error={passwordConfError}
/>
<ActivityIndicator animating={true} />
<Button
title="Register"
onPress={() => this.props.register(
name,
email,
mobileNo,
password)}
disabled={!email || !password || !name || !password || !passwordConf}
/>
</View>
</KeyboardAvoidingView>
);
}
}
const mapStateToProps = (state) => {
console.log(this.state);
//On screen load this executes with state = undefined, not sure what's causing it to fire
return {
loading : state.creatingUser
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({register: register}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(RegisterScreen);

React Redux tries to improve performance by doing shallow equality reference checks on incoming props in shouldComponentUpdate but you need to remember that
shouldComponentUpdate method is not called for the initial render therfore mapStateToProps will run in the initial load

Related

AsyncStorage use boolean from Promise

hi i'm new on react native and i have a issue with asyncStorage. I want to store the cache state in my pdf screen. The cache is a parameter of the source and handle only boolean. I made an onPress which change a state and store it in my localstorage, it works and when i console.log my getItem it shows true or false too it works too. But here is my problem. Now i want to just use the true or the false from this getItem because the parameter cache can handle boolean only. The best i could get on my search was Promise Boolean for my function. So if you could help me it'll be incredible because i really don't know. Thank you a lot and sorry for my English.
Here's my code //
export class Liste extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
navigation : props.navigation,
route: props.route,
selectedIndex : this.selectedIndex,
page : this.page,
numberOfPages : this.numberOfPages,
filePath : [],
cache : false,
};
}
saveCache() {
AsyncStorage.setItem('cache', JSON.stringify(this.state.cache));
console.log(`store ${this.state.cache}`);
}
async getCache () {
const ta = await AsyncStorage.getItem('cache', (value) => {
JSON.parse(value)
})
console.log(ta)
}
navigateBack = () => {
this.state.navigation.goBack();
};
BackAction = () => (
<TopNavigationAction icon={BackIcon} onPress={this.navigateBack}/>
);
render() {
const {files} = this.state.route.params;
const cache = this.state.cache;
const bool = this.getCache();
return (
<>
<TopNavigation style={{ borderWidth: 1 }} title='Mes Articles' alignment='center' accessoryLeft={this.BackAction} />
<ViewPager
selectedIndex={this.state.selectedIndex}
onSelect={ index => this.setState({ selectedIndex: index })}>
{files.map((file, i) =>
<Layout style={styles.tab} level='2'>
<Text>{file.filename}</Text>
<Text>Article: {i + 1} / {files.length} page: {this.state.page} / {this.state.numberOfPages}</Text>
<View>
<TopNavigationAction icon = {emailIcon} onPress={() => Share.open({ title: 'Pdf file', message: `bonjour voici l'article pdf ${file.filename}`, url: `file:///${this.state.filePath[i]}`, subject: `Article Pdf ${file.filename}` })} status='Partager'>
Partager
</TopNavigationAction>
<TopNavigationAction icon = {pin} onPress ={() => this.saveCache(cache === true ? this.setState({cache : false}) : this.setState({cache : true}))} status='Partager'>
Partager
</TopNavigationAction>
<TopNavigationAction icon = {pin} onPress ={() => console.log(this.getCache())} status='Partager'>
Partager
</TopNavigationAction>
</View>
<Pdf
source={{ uri: `http://10.1.0.248/${file.path}/${file.filename}`, cache : bool}}
style={styles.pdf}
enablePaging={true}
onLoadComplete={(numberOfPages, filePath) => {
this.state.filePath.push(filePath);
this.setState({ numberOfPages: numberOfPages });
}}
onPageChanged={(page, numberOfPages) => {
this.setState({ page: page });
}}
/>
</Layout>
)}
</ViewPager>
</>
);
}
}
You can use it like this.
await AsyncStorage.getItem('cache'); returns a JSON stringified value which you could parse and use.
async getCache () {
const ta = await AsyncStorage.getItem('cache');
console.log(JSON.parse(ta))
}
Use it likewise
let ta = await AsyncStorage.getItem('cache');
ta = JSON.parse(ta);

Variable "" has coerced Null value for NonNull type 'String!" GraphQl error

I am trying to save groupChatName as the value of the TextInput and save that name on the backend but in doing so I am getting the error "Possible unhandled promise, data: null, variable 'groupChatName' has coerced Null value for NonNull type 'String'", path: null.
export const createGroupChat = `mutation createGroupChat($groupChatName:String! $messages:String $createdUser:String! $users:String) {
createGroupChat(input:{
groupChatName:$groupChatName
messages:$messages
createdUser:$createdUser
users:$users
}){
groupChatName
messages
createdUser
users
}
}`;
const [currentUser, setCurrentUser] = useState('');
const [value, setValue] = useState('');
const [groupChatName, setGroupChatName] = useState('');
useEffect(() => {
Auth.currentAuthenticatedUser()
.then(currentUser => setCurrentUser(currentUser))
.catch (() => setUsername(null));
}, []);
GroupChat = () => {
if (value.length > 0) {
setValue('')
setGroupChatName('')
props.navigation.navigate('Message', { value });
}
};
GroupChatMutation = async () => {
const GroupChatDetails = { groupChatName, currentUser };
const newGroupChat = await API.graphql(graphqlOperation(createGroupChat, { GroupChatDetails }));
console.log(JSON.stringify(newGroupChat));
};
return (
<View style={styles.container}>
<Text style={styles.header}>Create GroupChat Name</Text>
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
multiline={true}
placeholder={'Type in a GroupChatName'}
placeholderTextColor="#abbabb"
value={value}
onChangeText={value => setValue(value)}
onChange={value => setGroupChatName(value)}
/>
.......```

Only Android users getting this error on createuserwithemailandpassword

On iOS this has never been an issue, but a lot of my users are attempting to create a firebase user, then I write that newly created user's info in the realtime database. It's hit or miss, some users it works successfully, sometimes it takes more than one try. Let me add that I have only been on this project for a short time and I can already tell best practices are not being used. The Following is the code:
Using crashlytics, I am seeing the folllwing error:
Fatal Exception: com.facebook.react.common.JavascriptException
null is not an object (evaluating 't.navigator.dispatch'), stack: #364:2006 value#49:1280 #605:1154 value#49:1280 #590:497 value#49:1280 value#28:3311 #28:822 value#28:2565 value#28:794 value#-1
screens/login.js
import React, { Component } from 'react';
import { ... } from 'react-native';
import { connect } from 'react-redux';
import { authActions, ... } from '../redux/actions';
import firebase from 'react-native-firebase';
class Login extends Component {
static navigationOptions = () => ({
headerMode: 'none',
header: null,
});
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
forceCheckEmail: false,
forceCheckPassword: false,
};
}
componentWillReceiveProps(newProps) {
const { props } = this;
const {
error,
isBusy,
dismissError,
screenProps: {
modal: {
setContent,
clearContent,
setDismissHandler,
},
},
} = newProps;
if (props.error !== error || props.isBusy !== isBusy) {
const modalContent =
isBusy ? <Spinner text='One moment...' /> :
error ? <ErrorPopup message={error} /> :
null;
if (modalContent) {
setContent(modalContent, undefined, this.ref);
setDismissHandler(() => {
this.setState({ showForgotBlock: true })
dismissError();
});
} else {
clearContent();
}
}
}
handleLogin() {
Keyboard.dismiss();
this.props.login({
email: this.state.email,
password: this.state.password,
});
}
render() {
const {
keyboardIsVisible,
email,
password,
forceCheckEmail,
forceCheckPassword,
showForgotBlock,
} = this.state;
const {
...
navigation: {
navigate
}
} = this.props;
const emailValid = validateEmail(email);
const passwordValid = password.length > 5;
const loginEnabled = email !== '' && emailValid && passwordValid;
const forgotPasswordBlock = showForgotBlock ? (
<TouchableOpacity
onPress={() => restorePassword(email)}
style={{marginTop: -20, marginBottom: 10}}
>
<Text style={{color: '#777'}}>
Forgot your password?
</Text>
</TouchableOpacity>
): null;
firebase.analytics().setCurrentScreen('login', 'login');
return (
...
<TextInput
style={[styles.input, forceCheckEmail && !emailValid ? styles.failedInput : null]}
autoCorrect={false}
placeholder="Email"
onBlur={() => this.setState({ forceCheckEmail: true })}
autoCapitalize="none"
keyboardType="email-address"
placeholderTextColor={color.INPUT_TEXT}
onChangeText={email => this.setState({ email })}
value={email}
/>
<TextInput
style={[styles.input, forceCheckPassword && !passwordValid ? styles.failedInput : null]}
autoCorrect={false}
placeholder="Password"
onBlur={() => this.setState({ forceCheckPassword: true })}
placeholderTextColor={color.INPUT_TEXT}
secureTextEntry
onChangeText={password => this.setState({ password })}
value={password}
/>
...
<TouchableOpacity
style={[styles.button, styles.buttonPrimary]}
onPress={() => navigate('SignUp')}
>
<Text style={styles.buttonPrimaryText}>
SIGN UP
</Text>
</TouchableOpacity>
...
export default connect(
state => ({
...
}),
{
login: data => authActions.login(data),
...
},
)(Login);
actions/auth.js
import { createActions } from 'redux-feline-actions';// I question this dependency
import firebase from 'react-native-firebase';
import FBSDK from 'react-native-fbsdk';
const usersDB = firebase.database().ref('users');
const newUserData = {
point: 0,
savedNumbers: [],
};
export default createActions({
...
register: ({ name, email, phone, password }) => ({
useReducer: 'auth',
payload: firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then(({user: { uid, email }}) => usersDB
.child(uid)
.set({
...newUserData,
name,
email,
phone,
id: uid,
})
.then(err => err || ({
...newUserData,
name,
email,
phone,
id: uid,
}))),
}),
...
stores/auth.js
import Immutable, { Map } from 'immutable';
import createAsyncStores from 'cat-stores'; // I also question this one
export default createAsyncStores({
auth: {
begin: state => state
.set('isBusy', true),
complete: (state, { payload }) => state
.set('isBusy', false)
.set('user', Immutable.fromJS(payload)),
error: {
default: (state, { payload }) => state
.set('error', payload.message)
.set('isBusy', false)
.set('user', null), // Android users keep getting this result I believe
},
},
...
},
Map({
isBusy: false,
error: null,
user: null,
redirectTo: null,
theme: Map(),
settings: Map(),
themeIsLoaded: false,
settingsAreLoaded: false,
}));
I expect the user to not have an issue with creating and saving new user info on Android, just like on iOS.

state undefined in react-native redux

I am implementing redux in react-native project. I have some asyn action and some pure actions. I am unable to get state value in my component. How do I get it.?
class Gender extends Component {
constructor(props) {
super(props);
}
nextScr = (gend) => {
alert(`gen: ${gend} \n this.props.gen: ${this.props.gen}`)
//***** here I am getting undefined ****
if(gend!= null) {
this.props.navigation.navigate('Info');
}
}
render() {
const { gen } = this.props;
return (
<View style={style.container}>
<View style={style.bcont}>
{/* this.storeData("Male") this.storeData("Female") */}
<Btn name="gender-male" txt="Male" click={() => this.props.saveMale('male')}
bstyl={(gen == 'Male') ? [style.btn, style.btnsel] : style.btn} />
<Text style={style.hi}>OR</Text>
<Btn name="gender-female" txt="Female" click={() => this.props.saveFemale('female')}
bstyl={(gen == 'Female') ? [style.btn, style.btnsel] : style.btn} />
</View>
<Text>Gender Value is: {this.props.gen}</Text>
// **** here not getting gen value ****
<Next name="chevron-right" nextClk={ () => this.nextScr(gen)} />
</View>
);
}
}
const mapStateToProps = state => {
const { gen } = state
return {
gen: gen,
};
};
const mapDispatchToProps = dispatch => {
return {
saveMale: (gen) => {
dispatch(saveMale(gen));
},
saveFemale: (gen) => {
dispatch(saveFemale(gen));
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Gender);
These are my actions:
export const saveMale = (gen) => ({
type: MALE_SAVE,
payload: gen
});
export const saveFemale = (gen) => ({
type: FEMALE_SAVE,
payload: gen
});
Following is my reducer:
const initialState = {
gen: null
}
export function genSave(state=initialState, action) {
switch(action.type) {
case MALE_SAVE:
alert(`state in MALE_SAVE: ${action.payload}`);
return { ...state, gen: action.payload };
case FEMALE_SAVE:
alert(`state in FEMALE_SAVE: ${action.payload}`);
return { ...state, gen: action.payload };
default:
alert(`state in default gender save: ${JSON.stringify(state)}`);
return state;
};
}
I am getting action.payload alert values but in the component I am not getting values. How do I solve this problem ?? Thanks in advance.
Can you try like this?
...
nextScr(gend) {
alert(`gen: ${gend} \n this.props.gen: ${this.props.gen}`)
if(gend!= null) {
this.props.navigation.navigate('Info');
}
}
render() {
const { gen } = this.props;
return (
<View style={style.container}>
<View style={style.bcont}>
{/* this.storeData("Male") this.storeData("Female") */}
<Btn name="gender-male" txt="Male" click={() => this.props.saveMale('male')}
bstyl={(gen == 'Male') ? [style.btn, style.btnsel] : style.btn} />
<Text style={style.hi}>OR</Text>
<Btn name="gender-female" txt="Female" click={() => this.props.saveFemale('female')}
bstyl={(gen == 'Female') ? [style.btn, style.btnsel] : style.btn} />
</View>
<Text>Gender Value is: {this.props.gen}</Text>
<Next name="chevron-right" nextClk={ () => this.nextScr(this.props.gen)} />
</View>
);
}
...
I believe your mapStateToProps could be the problem depending on how you initialize your store. Right now it assumes gen is a property on the base store but it is likely you have a combineRecucers call when you create the store that adds another object layer.

mapDispatchToProps, id is not defined in action

I get id is not defined in fetchEvents, when trying to use button.value as a parameter.
I am using mapDispatchToProps and mapStateToProps in my component.
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: setSubCategory => dispatch(fetchEvents(id))
};
};
const mapStateToProps = state => {
return {
setCredentials: state.setCredentials,
categories: state.fetchCategories,
isLoading: state.isLoading
};
};
I then destructure my props to get my id:
const {
fetchEvents,
resetForm,
isLoading,
setCredentials: { setStudent, setGroup, setYear }
} = this.props;
const id = setStudent || setGroup || setYear;
However, when I dispatch my action:
const buttonOptions = [
{
key: 0,
label: "refresh",
value: Id,
icon: "undo"
},
{
key: 1,
label: "back",
value: Id,
icon: "caret-left"
}
];
return (
<View style={styles.container}>
{buttonOptions.map((button, i) => {
const style =
i == 0 ? styles.divContainerLeft : styles.divContainerRight;
return (
<View style={style} key={"view" + i}>
<TouchableOpacity
disabled={isLoading ? true : false}
key={"TouchableOpacity" + i}
// dispatch action here
onPress={i == 0 ? () => fetchEvents(button.value) : resetForm}
>
<Icon
name={button.icon}
style={styles.button}
color="white"
key={"icon" + i}
size={30}
/>
</TouchableOpacity>
</View>
);
})}
</View>
);
In your mapDispatchToProps, you are re-declaring the parameter name to setSubCategory, yet, in your fetch call you pass id.
Instead, try this:
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: (setSubCategory) => dispatch(fetchEvents(setSubCategory))
};
};
OR
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: (id) => dispatch(fetchEvents(id))
};
};
This is because you are declaring and inlining fetchEvents as an anonymous function. The parameter names must match.