Redux didn't update component - react-native

I'm pretty new in React-Native. I'm trying to display a list of devices, then go to one device, go to its informations, change the name, and with redux to change the name on all "screens" but it didn't work. I thing I a little bit lost between props, state and global state...
All the navigation, views and APIs works well.
This is my list view:
class DeviceList extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
};
this._getDevices();
// BINDING
this._goToDevice = this._goToDevice.bind(this);
this._goToDeviceAdd = this._goToDeviceAdd.bind(this);
}
componentDidUpdate() {
console.log("DEVICE_LIST componentDidUpdate : ");
console.log(this.props.devices);
}
_goToDevice(id = number) {
this.props.navigation.navigate('DeviceDetail', { idDevice: id })
}
_getDevices() {
restGet('devices')
.then(data => {
// REDUX
const action = { type: "INIT_DEVICE_LIST", value: data.datas };
this.props.dispatch(action);
// STATE
this.setState({
isLoading: false
});
});
}
_displayList() {
if (!this.state.loading && this.props.devices && this.props.devices.length > 0) {
return (
<View style=>
<FlatList
// data={this.state.devices}
data={this.props.devices}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <DeviceItem device={item} goToDevice={this._goToDevice}
/>}
/>
</View>
)
}
}
render() {
return (
<View style={styles.main_container}>
{/* LOADING */}
<Loading loading={this.state.isLoading}/>
{/* LIST */}
{this._displayList()}
</View>
);
}
}
// REDUX
const mapStateToProps = (state) => {
return {
devices: state.devices
};
};
export default connect(mapStateToProps)(DeviceList);
This is my Device Detail screen:
class DeviceDetail extends React.Component {
constructor(props) {
super(props);
this.state = {
device: null,
isLoading: true,
};
//
this._getDevice();
}
componentDidUpdate() {
console.log("DEVICE_DETAIL componentDidUpdate : ");
console.log(this.props.devices);
}
_goToDeviceInfo() {
this.props.navigation.navigate('DeviceInfo', { deviceId: this.state.device.id })
}
_getDevice() {
restGet('devices/' + this.props.navigation.getParam('idDevice'))
.then(data => {
// REDUX
const action = { type: "UPDATE_DEVICE", value: data.datas };
this.props.dispatch(action);
// STATE
this.setState({
device: this.props.devices.find(d => d.id === this.props.navigation.getParam('idDevice')),
isLoading: false,
});
});
}
_displayTile() {
if (this.state.device) {
return (
<View>
<Text>{this.state.device.name}</Text>
<TouchableOpacity onPress={() => this._goToDeviceInfo()}>
<FontAwesomeIcon icon={ faCog } size={ 18 } />
</TouchableOpacity>
</View>
)
}
}
render() {
return (
<View style={styles.main_container}>
{/* LOADING */}
<Loading loading={this.state.isLoading}/>
{/* TILE */}
{this._displayTile()}
</View>
);
}
}
// REDUX
const mapStateToProps = (state) => {
return {
devices: state.devices
};
};
export default connect(mapStateToProps)(DeviceDetail);
My Device Info Screen
class DeviceInfo extends React.Component {
constructor(props) {
super(props);
this.state = {
// device: this.props.navigation.getParam('device')
// device: this.props.devices.find(d => d.id === this.props.navigation.getParam('deviceId')),
};
// BINDING
this._saveItem = this._saveItem.bind(this);
}
componentDidUpdate() {
console.log("DEVICE_INFO componentDidUpdate : ");
console.log(this.props.devices);
}
_saveItem(name) {
// DB
// restPost('devices', {'id': this.state.device.id, 'name': name})
console.log();
restPost('devices', {'id': this.props.devices.find(d => d.id === this.props.navigation.getParam('deviceId')).id, 'name': name})
.then(data => {
// REDUX
const action = { type: "UPDATE_DEVICE", value: data.datas };
this.props.dispatch(action);
});
}
_removeDevice() {
Alert.alert(
'Supprimer l\'Appareil',
'Voulez-vous vraiment supprimer cet Appareil ?',
[
{
text: 'Annuler',
style: 'cancel',
onPress: () => console.log('Cancel Pressed'),
},
{
text: 'Oui, je souhaite le supprimer.',
onPress: () => {
// REDUX
const action = { type: "DELETE_DEVICE", value: this.state.device };
this.props.dispatch(action);
this.props.navigation.navigate('DeviceList')
}
},
],
{cancelable: false},
);
}
render() {
// const device = this.state.device;
const device = this.props.devices.find(d => d.id === this.props.navigation.getParam('deviceId'));
return (
<View style={ styles.main_container }>
<DeviceInfoItem device={device} title={'NOM'} value={device.name} edit={true} saveItem={this._saveItem}/>
<View style={styles.footer}>
<TouchableOpacity style={styles.startButton} onPress={() => this._removeDevice()}>
<FontAwesomeIcon icon={ faTrash } style={styles.footer_element_icon}/>
<Text style={styles.footer_element_text}>Supprimer l'Appareil</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
// REDUX
const mapStateToProps = (state) => {
return {
devices: state.devices
};
};
export default connect(mapStateToProps)(DeviceInfo);
My Device Info Item Component
class DeviceInfoItem extends React.Component {
constructor(props) {
super(props);
this.state = {
displayForm: false,
};
this.editText = '';
console.log(this.props);
}
componentDidUpdate() {
console.log("DEVICE_INFO_ITEM componentDidUpdate");
}
_displayValue() {
if (this.props.edit && this.state.displayForm) {
return (
<View>
<Input
placeholder={this.props.title}
// value={value}
leftIcon={<FontAwesomeIcon icon={ faTag } size={ 10 } />}
leftIconContainerStyle={ styles.inputIcon }
onChangeText={(text) => {
this.editText = text;
}}
/>
</View>
);
} else {
return (
<View>
<Text style={ styles.title }>{ this.props.title }</Text>
<Text style={ styles.value }>{ this.props.value }</Text>
</View>
);
}
}
_displayButton() {
if (this.props.edit) {
if (!this.state.displayForm) {
return (
<TouchableOpacity style={styles.edit} onPress={() => {
this.setState({
displayForm: true
})
}}>
<FontAwesomeIcon icon={ faPen } style={ styles.info_icon } size={ 14 } />
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity style={styles.edit} onPress={() => {
this.props.saveItem(this.editText);
this.setState({
displayForm: false,
});
}}>
<FontAwesomeIcon icon={ faCheck } style={ styles.info_icon } size={ 14 } />
</TouchableOpacity>
);
}
}
}
render() {
return (
<View>
<View style={ styles.line }>
{ this._displayValue() }
{ this._displayButton() }
</View>
<Divider style={ styles.divider } />
</View>
);
}
}
export default DeviceInfoItem;
And my reducer:
const initialState = {
devices: []
};
function updateDevices(state = initialState, action) {
let nextState;
// CHECK IF DEVICE EXIST
const deviceIndex = state.devices.findIndex(device => device.id === action.value.id);
let device = state.devices.find(device => device.id === action.value.id);
switch (action.type) {
case 'INIT_DEVICE_LIST':
console.log('INIT_DEVICE_LIST');
nextState = {
...state,
devices: action.value
};
return nextState || state;
case 'ADD_DEVICE':
console.log('ADD_DEVICE');
nextState = {
...state,
devices: [...state.devices, action.value]
};
return nextState || state;
case 'DELETE_DEVICE':
console.log('DELETE_DEVICE');
if (deviceIndex !== -1) {
nextState = {
...state,
devices: state.devices.filter( (item, index) => index !== deviceIndex)
}
}
return nextState || state;
case 'UPDATE_DEVICE':
console.log('UPDATE_DEVICE');
if (deviceIndex !== -1) {
let d = state.devices;
d[deviceIndex] = action.value;
nextState = {
...state,
devices: d
}
}
return nextState || state;
default:
return state
}
}
export default updateDevices;
Delete Device works very well, the devices list in well updated. But the name updating didn't work. When I save, I got a console log ('DEVICE_INFO_ITEM componentDidUpdate) but not ('DEVICE_INFO componentDidUpdate). Why ?

create a file index.js in your reducer folder and do this:
import { combineReducers } from 'redux';
import myreducer from './Reducerfile';
export default combineReducers({
reducer: myreducer
});
And then
// REDUX
const mapStateToProps = (state) => {
return {
devices: state.reducer.devices
};
};
export default connect(mapStateToProps)(DeviceInfo);

Related

React Native Select Contacts Checkmark with FlatList

I'm new to react native and i am trying to make a contacts checkmark from FlatList with Large data items.
We can select one or more items and I want to pass these all selected contacts to another screen when i clicked on DONE button ..
like this in the picture
React Code
import React, { Component } from 'react'
import {
View,
Text,
Dimensions,
TouchableOpacity,
FlatList,
ActivityIndicator,
Image,
TextInput,
PermissionsAndroid,
Platform,
} from 'react-native'
import Feather from "react-native-vector-icons/Feather"
import ContactsLib from 'react-native-contacts'
import { styles } from './ContactStyles'
import PropTypes from 'prop-types'
export class Contacts extends Component {
static navigationOptions = ({navigation}) => {
return {
headerTitle: () => (
<View style={{flex: 1, alignSelf: 'center'}}>
<Text style={{
color: '#fff',
alignSelf: 'center',
fontSize: 22,
}}>Contacts</Text>
</View>
),
headerRight: () => (
<TouchableOpacity onPress={() => navigation.goBack(null)}
style={{right: Platform.OS === 'ios' ? Dimensions.get("window").height < 667 ? '10%' : '5%' : '25%', backgroundColor: 'black', paddingLeft: 15}}>
<Text style={{fontSize: 18, color: '#fff'}}>Done</Text>
</TouchableOpacity>
),
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack(null)} style={{left: Dimensions.get("window").height < 667 ? '8%' : '3%', backgroundColor: 'black', width: '100%'}}>
<Feather style = {{paddingLeft : 10}} name="chevron-left" size={26} color="white" />
</TouchableOpacity>
),
headerStyle: {
backgroundColor: 'black',
},
headerTintColor: '#fff',
};
};
constructor(props) {
super(props)
this.state = {
contactList: [],
selectedContact: [],
text: '',
isLoading: true,
}
this.arrayholder = []
}
async componentDidMount() {
if (Platform.OS === 'android') {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
{
title: 'App Contact Permission',
message: 'This App needs access to your contacts ',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
)
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
this.getListOfContacts()
} else {
this.setState({ isLoading: false })
this.getOtherContacts()
}
} catch (err) {
this.setState({ isLoading: false })
}
} else {
ContactsLib.checkPermission((err, permission) => {
if (permission === 'denied') {
this.setState({ isLoading: false })
this.getOtherContacts()
} else {
this.getListOfContacts()
}
})
}
}
// Mics Method
getOtherContacts = () => {
const { otherContactList } = this.props
const arrFinal = []
if (otherContactList.length > 0) {
otherContactList.map((listItem) => {
arrFinal.push(listItem)
})
}
arrFinal.map((listItem, index) => {
listItem.isSelected = false
listItem.id = index
})
this.setState({ contactList: arrFinal, isLoading: false })
this.arrayholder = arrFinal
}
getListOfContacts = () => {
const { otherContactList } = this.props
const arrFinal = []
ContactsLib.getAll((err, contacts) => {
if (err) {
throw err
}
contacts.map((listItem) => {
arrFinal.push({
fullname: listItem.givenName + ' ' + listItem.familyName,
phoneNumber: listItem.phoneNumbers.length > 0 ? listItem.phoneNumbers[0].number : '',
avatar: listItem.thumbnailPath,
})
})
if (otherContactList.length > 0) {
otherContactList.map((listItem) => {
arrFinal.push(listItem)
})
}
arrFinal.map((listItem, index) => {
listItem.isSelected = false
listItem.id = index
})
this.setState({ contactList: arrFinal, isLoading: false })
this.arrayholder = arrFinal
})
}
getSelectedContacts = () => {
const { selectedContact } = this.state
return selectedContact
}
checkContact = (item) => {
const { onContactSelected, onContactRemove } = this.props
let arrContact = this.state.contactList
let arrSelected = this.state.selectedContact
arrContact.map((listItem) => {
if (listItem.id === item.id) {
listItem.isSelected = !item.isSelected
}
})
if (item.isSelected) {
arrSelected.push(item)
if (onContactSelected) {
onContactSelected(item)
}
} else {
if (onContactRemove) {
onContactRemove(item)
}
arrSelected.splice(arrSelected.indexOf(item), 1)
}
this.setState({ contactList: arrContact, selectedContact: arrSelected })
}
checkExist = (item) => {
const { onContactRemove } = this.props
let arrContact = this.state.contactList
let arrSelected = this.state.selectedContact
arrContact.map((listItem) => {
if (listItem.id === item.id) {
listItem.isSelected = false
}
})
if (onContactRemove) {
onContactRemove(item)
}
arrSelected.splice(arrSelected.indexOf(item), 1)
this.setState({ contactList: arrContact, selectedContact: arrSelected })
}
SearchFilterFunction = (text) => {
let newArr = []
this.arrayholder.map(function(item) {
const itemData = item.fullname.toUpperCase()
const textData = text.toUpperCase()
if (itemData.indexOf(textData) > -1) {
newArr.push(item)
}
})
this.setState({
contactList: newArr,
text: text,
})
}
//Render Method
_renderSeparator = () => {
const { sepratorStyle } = this.props
return <View style={[styles.sepratorStyle, sepratorStyle]} />
}
_renderItem = ({ item }) => {
const { viewCheckMarkStyle } = this.props
return (
<TouchableOpacity onPress={() => this.checkContact(item)}>
<View style={styles.viewContactList}>
<Image
source={item.avatar !== '' ? { uri: item.avatar } : require('./Resources/user.png')}
style={styles.imgContactList}
/>
<View style={styles.nameContainer}>
<Text style={styles.txtContactList}>{item.fullname}</Text>
<Text style={styles.txtPhoneNumber}>{item.phoneNumber}</Text>
</View>
{item.isSelected && (
<Image
source={require('./Resources/check-mark.png')}
style={[styles.viewCheckMarkStyle, viewCheckMarkStyle]}
/>
)}
</View>
</TouchableOpacity>
)
}
_renderItemHorizontal = ({ item }) => {
const { viewCloseStyle } = this.props
return (
<View style={styles.viewSelectedContactList}>
<Image
source={item.avatar !== '' ? { uri: item.avatar } : require('./Resources/user.png')}
style={styles.imgSelected}
/>
<TouchableOpacity
onPress={() => this.checkExist(item)}
style={[styles.viewCloseStyle, viewCloseStyle]}
>
<Image source={require('./Resources/error.png')} style={styles.imgCancelStyle} />
</TouchableOpacity>
<Text style={styles.txtSelectedContact} numberOfLines={1}>
{item.fullname}
</Text>
</View>
)
}
render() {
const { searchBgColor, searchPlaceholder, viewSepratorStyle } = this.props
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<View style={[styles.viewSearch, { backgroundColor: searchBgColor }]}>
<TextInput
style={styles.searchInput}
placeholder={searchPlaceholder}
onChangeText={this.SearchFilterFunction}
/>
</View>
<View>
<FlatList
showsHorizontalScrollIndicator={false}
data={this.state.selectedContact}
extraData={this.state}
renderItem={this._renderItemHorizontal}
horizontal
/>
</View>
{this.state.selectedContact.length > 0 && (
<View style={[styles.viewSepratorStyle, viewSepratorStyle]} />
)}
{this.state.contactList.length > 0 && (
<FlatList
ListFooterComponent={this._renderSeparator}
ItemSeparatorComponent={this._renderSeparator}
showsVerticalScrollIndicator={false}
data={this.state.contactList}
renderItem={this._renderItem}
onEndReachedThreshold={0.3}
extraData={this.state}
keyExtractor={(item) => item.id.toString()}
/>
)}
{this.state.isLoading && (
<View style={styles.loading}>
<ActivityIndicator animating={true} size="large" color="gray" />
</View>
)}
</View>
)
}
}
StyleSheet
Contacts.propTypes = {
otherContactList: PropTypes.array,
viewCloseStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
viewCheckMarkStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
sepratorStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
viewSepratorStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
searchBgColor: PropTypes.string,
searchPlaceholder: PropTypes.string,
onContactSelected: PropTypes.func,
onContactRemove: PropTypes.func,
}
Contacts.defaultProps = {
otherContactList: [],
viewCloseStyle: {},
viewCheckMarkStyle: {},
sepratorStyle: {},
viewSepratorStyle: {},
searchBgColor: 'rgb(202,201,207)',
searchPlaceholder: 'Search...',
onContactSelected: () => {},
onContactRemove: () => {},
}
export default Contacts
Please Help me..
Thanks.....

Why react-native doesn't render all content when 2 render methods included in it?

I have some content to be rendered conditionally and some fixed content i.e. footer. I dont want to render footer every time when state changes, hence I've added two methods renderContent() and renderFooter to be called in render() method.
Below code, doesn't render both methods.
'use strict';
import React, { Component } from 'react';
import { Alert, FlatList, View, StyleSheet, Text, Linking, Button } from 'react-native';
import { AsyncStorage } from 'react-native';
import getEnvVars from '../environment';
const { apiUrl } = getEnvVars();
import Moment from 'moment';
import { Ionicons } from '#expo/vector-icons';
import FootBar from '../screens/FootBar';
import { LinesLoader } from 'react-native-indicator';
export default class SubscriptionsToEnd extends Component {
static navigationOptions = ({ navigation }) => {
const { state } = navigation;
return {
title: `${state.params && state.params.title ? state.params.title : 'Subscriptions Due'}`,
};
};
constructor(props) {
super(props);
this.state = {
isLoaded: false,
dataSource: [],
title: 'Subscriptions Due'
};
}
componentDidMount() {
this._getAllCustomers();
}
_getAllCustomers() {
let url;
if (this.state.title === 'Subscriptions Due') {
url = apiUrl + "/customersWithSubscriptionNearToEnd/";
this.props.navigation.setParams({ title: 'Subscriptions Due' })
}
if (this.state.title === 'Customers') {
url = apiUrl + "/customers/";
this.props.navigation.setParams({ title: 'Customers' })
}
this.setState({ isLoaded: false })
try {
AsyncStorage.multiGet(['role', 'jwt']).then((data) => {
let role = data[0][1];
let jwt = data[1][1];
if (role === 'Admin') {
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'jwt': jwt
},
}).then(res => res.json())
.then(
(result) => {
if (result.message != 'Unauthorized user!' && this.state.title === 'Customers') {
this.setState({
isLoaded: true,
dataSource: result,
title: 'Subscriptions Due'
});
} else if (result.message != 'Unauthorized user!' && this.state.title === 'Subscriptions Due') {
this.setState({
isLoaded: true,
dataSource: result,
title: 'Customers'
});
} else if (result.message === 'Unauthorized user!') {
this.props.navigation.navigate('Login');
}
},
(error) => {
console.log(error);
this.setState({
isLoaded: true
});
this.props.navigation.navigate('Login');
}
)
}
})
} catch (error) {
console.log('Error at getting token \n' + error)
}
}
GetGridViewItem(id) {
Alert.alert(id);
}
_logOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
_addCustomer() {
// TBD
}
renderContent() {
if (!this.state.isLoaded) {
return (
<View style={styles.loader}>
<LinesLoader color='#1d91a5' barWidth={5} barHeight={60} barNumber={5} betweenSpace={5} />
</View>
)
}
if (this.state.isLoaded) {
return (
<View style={styles.container}>
<View style={styles.grid}>
<FlatList
data={this.state.dataSource}
renderItem={({ item }) =>
<View style={styles.GridViewContainer}>
<Text style={styles.GridViewTextLayout}>
<Text onPress={this.GetGridViewItem.bind(this, item._id)}>
<Text style={styles.Name}>{item.firstname}</Text> <Text style={styles.Name}>{item.lastname}</Text> {"\n"}
<Text>{Moment(item.till_date).format('Do MMM YYYY')} </Text>{"\n\n"}
</Text>
<Text onPress={() => { Linking.openURL('tel:+44' + item.mobile); }}><Ionicons name="md-phone-portrait" size={22} color="#1d91a5" /> {item.mobile}</Text> {"\n\n"}
<Text><Ionicons name="md-mail" size={22} color="#1d91a5" />{item.email}</Text>
</Text>
</View>}
numColumns={2}
keyExtractor={(item, index) => index.toString()}
/>
</View >
</View>
)
};
}
renderFooter() {
return (
<View style={styles.buttonsContainer}>
<View style={styles.button}>
<Button color='#1d91a5' title={this.state.title} onPress={this._getAllCustomers.bind(this)} />
</View>
<View style={styles.button}>
<Button color='#1d91a5' title="+Customer" onPress={this._addCustomer.bind(this)} />
</View>
<View style={styles.button}>
<Button color='#1d91a5' title="Logout" onPress={this._logOutAsync.bind(this)} />
</View>
</View>
);
}
render() {
return (
this.renderContent(),
this.renderFooter()
);
}
}
Above code only renders this.renderFooter() method. If I swap methods in render(), it renders this.renderContent().
Can someone please tell me why it is failing to render both?
I was doing it wrong. Main render() method should be like:
render() {
return (
<View style={styles.wrapper}>
{this.renderContent()}
{this.renderFooter()}
</View>
);
}
It looks like you figured it out just before I could post my answer.
The return function can only return one view. Your 2 functions each return a view. So wrapping both functions in a single view solves the problem.

Passing data from one component to another in React Native

I am setting Sub Domain URL's for single app. Sub domain name will enter at the first time. it saves to the async storage and need to retrieve it from a common component
Using the const, it's not working properly.
Here is the partially completed code. baseURL and socketURL is needed inside another component function. How can I access these constants from there ?
index_new.js
import * as React from 'react';
import { View } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
import Login from "../screens/common/login/login/login";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
sub_domain: '',
};
}
async getSchoolCode() {
let sub_domain = '';
try {
sub_domain = await AsyncStorage.getItem('SCHOOL_CODE') || ''
} catch (error) {
}
return sub_domain;
};
async setSubdomain() {
const sub_domain = await this.getschoolcode()
await this.setState({ sub_domain })
}
getBaseUrl() {
return `http://${this.state.sub_domain}.vidhyadhan.in:81/`;
}
getSocketIoUrl() {
return `http://${this.state.sub_domain}.vidhyadhan.in:8080/`;
}
async componentDidMount() {
await this.setSubdomain();
}
render() {
const baseUrl = this.getBaseUrl();
const socketIoUrl = this.getSocketIoUrl();
const extraProps = {
baseUrl,
socketIoUrl
}
return (
<View>
<Login {...extraProps} />
</View>
)
}
}
Login.js
import React, { Component } from 'react'
import {
Alert,
Keyboard,
Text,
View,
TextInput,
TouchableHighlight,
Image,
ActivityIndicator,
StatusBar,
} from 'react-native'
import config from "../../../../config";
import styles from './style'
import { Icon } from "react-native-elements";
import Toaster from '../../../../components/toaster'
import AsyncStorage from '#react-native-community/async-storage';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
credentials: {
schoolcode: "",
email: "",
password: "",
},
loading: false,
school_code: '',
};
}
async getschoolcode() {
let school_code = '';
try {
school_code = await AsyncStorage.getItem('SCHOOL_CODE') || ''
} catch (error) {
}
return school_code;
};
updateText(text, field) {
let newCredentials = Object.assign(this.state.credentials);
newCredentials[field] = text;
// setState should be done like this
this.setState({
credentials: newCredentials
})
if(field == 'schoolcode'){
AsyncStorage.setItem('SCHOOL_CODE', text);
this.getschoolcode().then((keyValue) => {
this.state.school_code = keyValue;
console.log(this.state.school_code);
});
}
}
async login() {
Keyboard.dismiss();
let credentials = this.state.credentials;
if (this.state.credentials.schoolcode == '' || this.state.credentials.email == '' || this.state.credentials.password == '') {
Toaster.toast('Please Enter a valid UserName and Password', '#d30000')
} else {
const that = this;
credentials.email = that.state.credentials.email;
this.setState({ loading: !this.state.loading });
const new_url = this.props.baseUrl;
fetch(config.baseURL + 'mobileapi/get_token/?username=' + `${that.state.credentials.email}` + '&password=' + `${that.state.credentials.password}`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
credentials: credentials,
}),
})
.then((response) => response.json())
.then(responseJson => {
if (responseJson.confirmation === "success") {
AsyncStorage.setItem('USER_ID', responseJson.data.user_id.toString());
this.setState({ loading: !this.state.loading });
setTimeout(() => {
this.props.navigation.navigate("Home")
}, 500);
} else {
this.setState({ loading: !this.state.loading });
setTimeout(() => {
Toaster.toast('Please Enter a valid UserName and Password', '#d30000')
// throw new Error(responseJson.message);
}, 500);
}
})
.catch((err) => {
//stop loading
this.setState({ loading: !this.state.loading });
setTimeout(() => {
if (JSON.stringify(err.message) === JSON.stringify('Network request failed')) {
Toaster.toast('Please check your internet connection or try again later', '#d30000')
}
}, 500);
})
}
}
render() {
const loginText = (this.state.loading) ? 'Loading' : 'Login';
return (
<View style={styles.container}>
<StatusBar backgroundColor="#2383c9"
translucent={true}
hidden={false}/>
<Image source={require('../../../../assets/images/icons/logo.png')}
style={{ width: 99, height: 99, margin: 5, }} />
<Text style={{ fontSize: 20, margin: 20, color: "#ffffff" }}>Vidhyadhan</Text>
<View style={styles.inputContainer}>
<Image style={styles.inputIcon}
source={require('../../../../assets/images/icons/username.png')} />
<TextInput style={styles.inputs}
placeholder="School-Code"
underlineColorAndroid='transparent'
onChangeText={text => {
this.updateText(text, 'schoolcode')
}} value={this.state.schoolcode}
autoCorrect={false}
autoCapitalize={"none"}
/>
</View>
<View style={styles.inputContainer}>
<Image style={styles.inputIcon}
source={require('../../../../assets/images/icons/username.png')} />
<TextInput style={styles.inputs}
placeholder="Username"
keyboardType="email-address"
underlineColorAndroid='transparent'
onChangeText={text => {
this.updateText(text, 'email')
}} value={this.state.email}
autoCorrect={false}
autoCapitalize={"none"}
/>
</View>
<View style={styles.inputContainer}>
<Image style={styles.inputIcon}
source={require('../../../../assets/images/icons/password.png')} />
<TextInput style={styles.inputs}
placeholder="Password"
secureTextEntry={true}
underlineColorAndroid='transparent'
onChangeText={text => {
this.updateText(text, 'password')
}}
value={this.state.password}
autoCorrect={false}
secureTextEntry />
</View>
<TouchableHighlight style={[styles.buttonContainer, styles.loginButton]}
onPress={this.login.bind(this)} >
<View style={{ justifyContent: 'center', flex: 1, flexDirection: 'row' }}>
{this.state.loading === false ?
<Icon name='login' type='entypo' size={16} color='white' /> :
<ActivityIndicator size="small" color="#ffffff" />}
<Text style={styles.loginText}> {loginText} </Text>
</View>
</TouchableHighlight>
</View>
);
}
}
export default Login;
First, You're not setting the state correctly. Then, you're trying to set the state twice.
Here's a better way of doing it:
import * as React from 'react';
import { View } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
sub_domain: '',
};
}
async getSchoolCode() {
let sub_domain = '';
try {
sub_domain = await AsyncStorage.getItem('sub_domain') || ''
} catch (error) {
}
return sub_domain;
};
async setSubdomain() {
const sub_domain = await this.getschoolcode()
await this.setState({ sub_domain })
}
getBaseUrl() {
return `http://${this.state.sub_domain}.vidhyadhan.in:81/`;
}
getSocketIoUrl() {
return `http://${this.state.sub_domain}.vidhyadhan.in:8080/`;
}
async componentDidMount() {
await this.setSubdomain();
}
render() {
const baseUrl = this.getBaseUrl();
const socketIoUrl = this.getSocketIoUrl();
return (
<View/>
);
}
}
Based on the comments, here's how your render can be:
render() {
const baseUrl = this.getBaseUrl();
const socketIoUrl = this.getSocketIoUrl();
const extraProps = {
baseUrl,
socketIoUrl
}
return (
<View>
<MyFirstComponent {...extraProps} />
<MySecondComponent {...extraProps} />
</View>
)
}
And in your MyFirstComponent, you can either use this.props.baseUrl or this.props.socketIoUrl

How to expand and collapse specify section using SecionList?

I call an api https://obscure-reaches-65656.herokuapp.com/api/getCloseTime?city=Miaoli&sTime=21&eTime=24 to my react-redux action and use SectionList to show the data.
With official tutorial, i use SectionList it will just show all of the section, i try to find the way when click title that can expand or collapse the section.
I find my sectionComp and renderSectionItem use the same title so i try use this.state{ title: '', expand: false }
When i click 國興戲院 use this.setState({ title: '國興戲院', expand: true }) and use like if(this.state.expand) {} in renderSectionItem
Obviously its not working because i may have a lot of section.
I have no idea what next step should i try.
Any help would be appreciated. Thanks in advance.
Here is my SectionList data:
Here is my class component:
import React, { Component } from 'react';
import { Text, SectionList, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { View } from 'react-native-animatable';
import { fetchSearchTime } from '../actions';
import { Spinner } from './common';
import GetUserTime from '../function/common/GetUserTime';
class MovieCloseTime extends Component {
constructor(props) {
super(props);
const { selectedCity, firstSliderValue, secondSliderValue }
= this.props.navigation.state.params;
this.state = {
selectedCity,
firstSliderValue,
secondSliderValue,
};
}
componentDidMount() {
const { selectedCity, firstSliderValue, secondSliderValue } = this.state;
this.props.fetchSearchTime({
selectedCity, firstSliderValue, secondSliderValue
});
}
sectionComp = (info) => {
const theaterCn = info.section.title;
console.log('Title info is =>');
console.log(info);
return (
<TouchableOpacity
onPress={() => console.log('Hot to expand and collapse specify section when click here?')}
>
<View style={{ flex: 1, backgroundColor: '#DCDCDC' }}>
<Text style={styles.sectionTitle}>{theaterCn}</Text>
</View>
</TouchableOpacity>
);
}
renderSectionItem = (info) => {
const cnName = info.item.cnName;
console.log('Section info is =>');
console.log(info);
return (
<TouchableOpacity
onPress={() => {
this.props.navigation.navigate('MovieDetail', {
enCity: this.state.selectedCity,
cnName
});
}
}
>
<View style={{ flex: 1, flexDirection: 'column' }}>
<Text style={styles.sectionSubTitle}>{cnName}</Text>
<View style={{ flexDirection: 'row', flexWrap: 'wrap', backgroundColor: '#F8F8FF' }}>
{info.item.releasedTime.map((value, index) => {
const theTime = GetUserTime.getAsiaTime(value, 'YYYY/MM/DD HH:mm:ss');
const hour = theTime.getHours();
const minute = (theTime.getMinutes() < 10 ? '0' : '') + theTime.getMinutes();
return (
<Text style={styles.sectionTimeTitle} key={index}>{`${hour}:${minute}`}</Text>
);
})
}
</View>
</View>
</TouchableOpacity>
);
}
render() {
const movieData = this.props.searchTime;
if (this.props.loading) {
return <Spinner text='Loading...' />;
}
console.log('movieData is =>');
console.log(movieData);
return (
<View style={{ flex: 1 }}>
<SectionList
renderSectionHeader={this.sectionComp}
renderItem={this.renderSectionItem}
sections={movieData}
keyExtractor={(item, index) => index}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
</View>
);
}
}
const mapStateToProps = (state) => {
const searchTime = state.searchTime.searchList;
const loading = state.searchTime.loading;
return { searchTime, loading };
};
const styles = {
// some styles
};
export default connect(mapStateToProps, { fetchSearchTime })(MovieCloseTime);
Here is my action fetchSearchTime:
export const fetchSearchTime = ({ selectedCity, firstSliderValue, secondSliderValue }) => {
return (dispatch) => {
dispatch({ type: SEARCH_TIME_REQUEST });
console.log(`https://obscure-reaches-65656.herokuapp.com/api/getCloseTime?city=${selectedCity}&sTime=${firstSliderValue}&eTime=${secondSliderValue}`);
fetch(`https://obscure-reaches-65656.herokuapp.com/api/getCloseTime?city=${selectedCity}&sTime=${firstSliderValue}&eTime=${secondSliderValue}`)
.then(response => response.json())
.then(responseData => {
const movieData = responseData.reduce((r, s) => {
r.push({ title: s.theaterCn, id: s._id, expand: true, data: s.movie });
return r;
}, []);
//dispatch({ type: SEARCH_TIME, payload: responseData });
dispatch({ type: SEARCH_TIME, payload: movieData });
})
.catch((error) => console.log(error));
};
};
about type SEARCH_TIME reducer:
// with others type
import {
SEARCH_TIME_REQUEST,
SEARCH_TIME
} from '../actions/types';
const INITIAL_STATE = {
searchList: [],
loading: true,
selectedCity: '',
firstSliderValue: '',
secondSliderValue: ''
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SEARCH_TIME_REQUEST:
return {
searchList: [],
loading: true,
};
case SEARCH_TIME:
return {
searchList: action.payload,
loading: false
};
default:
return state;
}
};

Navigation is not called right after user is unauthenticated

I am trying to logout the user and then send him to SignedOut screen but when I press on Sign Out it is calling the unauthUser function but then it restart the switch navigator to dashboard and I have to go in profile screen to tap again on Sign Out to go out. Any idea what am I doing wrong in all this?
Here is my button from Profile.js
<TouchableOpacity
onPress={() => onSignOut().then(() => {
this.props.dispatch(unauthUser)
navigation.navigate("SignedOut")
}
)}
>
<View style={styles.signOutButton}>
<Text style={styles.button}>SIGN OUT</Text>
</View>
</TouchableOpacity>
onSignOut() from Auth.js
export let USER_KEY = 'myKey';
export const onSignIn = async () => { await AsyncStorage.setItem(USER_KEY, 'true') };
export const onSignOut = async () => { await AsyncStorage.removeItem(USER_KEY) };
export const isSignedIn = () => {
return new Promise((resolve, reject) => {
AsyncStorage.getItem(USER_KEY)
.then(res => {
if (res !== null) {
// console.log('true')
resolve(true);
} else {
resolve(false);
// console.log('false')
}
})
.catch(err => reject(err));
});
};
unauthUser from authActions.js
exports.unauthUser = {
type: 'UNAUTH_USER'
}
and from authReducer.js
case 'UNAUTH_USER':
return {
user_id: undefined,
token: undefined
};
and here is my switch navigator
export const createRootNavigator = (signedIn = false) => {
return SwitchNavigator(
{
SignedIn: {
screen: SignedIn
},
SignedOut: {
screen: SignedOut
}
},
{
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
);
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
signedIn: false,
checkedSignIn: false
};
}
async componentWillMount() {
await isSignedIn()
.then(res => this.setState({ signedIn: res, checkedSignIn: true}))
.catch(err => alert("An error occurred"));
}
render() {
const { checkedSignIn, signedIn } = this.state;
// If we haven't checked AsyncStorage yet, don't render anything (better ways to do this)
if (!checkedSignIn) {
return null;
}
const Layout = createRootNavigator(signedIn);
return (
<SafeAreaView style={styles.safeArea}>
<View style={{flex: 1, backgroundColor: '#ffffff'}}>
<StatusBar barStyle="light-content"/>
<Layout />
<AlertContainer/>
</View>
</SafeAreaView>
)
}
};