List Item Data is populated but the data does not display on flat list - react-native

In this FlatList, the list items Display but they are blank.
I have set break points and the data is there, but it only displays blank list items.
I think the issue may arise when I am setting the array empty, then it does not re-render properly when the fetch is complete.
Crypto Component
import {StyleSheet, FlatList} from 'react-native';
import {getCrypto} from '../api_service/crypto';
import CryptoItem from '../data/cryptoItem';
export default class CryptoMarket extends Component {
constructor(props) {
super(props);
this.state = {cryptos: [], refreshing: true};
this.fetchCrypto = this.fetchCrypto.bind(this);
}
componentDidMount() {
this.fetchCrypto();
}
fetchCrypto() {
getCrypto()
.then(cryptos => this.setState({cryptos, refreshing: false}))
.catch(() => this.setState({refreshing: false}));
}
handleRefresh() {
this.setState(
{
refreshing: true,
},
() => this.fetchCrypto(),
);
}
render() {
return (
<FlatList
data={this.state.cryptos}
renderItem={({item}) => <CryptoItem crypto={{item}} />}
keyExtractor={item => item.name}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh.bind(this)}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0f0f0f',
},
contentContainer: {
marginTop: 50,
alignItems: 'center',
paddingHorizontal: 20,
},
title: {
fontSize: 20,
color: '#fff',
},
});
Fetch API
export async function getCrypto() {
try {
const fetchCrypto = await fetch(
'https://api.nomics.com/v1/currencies/ticker?key=***&ids=BTC,ETH,XRP',
);
const displayCrypto = await fetchCrypto.json();
console.log(displayCrypto);
return displayCrypto;
} catch (error) {
throw error;
}
}
List Item
import React, {Component} from 'react';
import {ListItem} from 'react-native-elements';
export default class CryptoItem extends Component {
render() {
const {name, logo_url, price} = this.props.crypto;
return (
<ListItem
title={name}
text={'Price : ' + price}
leftAvatar={{source: {uri: logo_url}}}
bottomDivider={true}
/>
);
}
}

Related

React Native, how to execute a component's method from App.js when using stack navigation

I finished a React Native course, and I'm trying to make a chat app to practice.
Summarize the problem:
I have 2 screens ,ContactList.js and ChatRoom.js
I have a Navigation stack with these two screens Navigation.js
The Navigation component is imported and rendered in App.js
I added FCM module to handle notifications
The goal is to execute the function that loads messages in the chatroom _loadMessages(), when the app receives a notification on foreground state. And to execute the function (I didn't create it yet) to update unread message in a global state.
What I've tried
I followed react native firebase docs, I have a function that handle notification on foreground declared inside App.js. The problem is that I can't tell the other components (the screens) to execute their functions. The "Ref" method can't be used cause I'm not calling the child component (the screens) directly inside the App.js, I'm calling and rendering the Navigation.js Stack instead.
So, in this case, when we have a navigation component called on app.js, how can we tell other components to execute a function that is declared inside them?
App.js
import React, { useEffect } from 'react'
import Navigation from './Navigation/Navigation'
import messaging from '#react-native-firebase/messaging';
export default function App() {
requestUserPermission = async () => {
//On récupere le token
const token = await messaging().getToken();
console.log('TOKEN: ' + token)
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
}
}
handleForegroundNotification = () => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('A new FCM message arrived!', JSON.stringify(remoteMessage));
});
return unsubscribe;
}
useEffect(() => {
this.requestUserPermission();
this.handleForegroundNotification();
}, []);
return (
<Navigation />
)
}
Navigation.js
import { createAppContainer } from "react-navigation"
import { createStackNavigator } from "react-navigation-stack"
import ContactList from '../Components/ContactList'
import ChatRoom from '../Components/ChatRoom'
const ChatStackNavigator = createStackNavigator({
ContactList: {
screen: ContactList,
navigationOptions: {
title: 'Contacts'
}
},
ChatRoom: {
screen: ChatRoom,
navigationOptions: {
title: 'Conversation'
}
}
})
export default createAppContainer(ChatStackNavigator)
ChatRoom.js
import React from 'react'
import { View, StyleSheet, Text, Image, SafeAreaView, TextInput, Alert, FlatList, ActivityIndicator } from 'react-native'
import { sendMessage } from '../API/sendMessageApi'
import { getMessages } from '../API/getMessagesApi'
import MessageItem from './MessageItem'
class ChatRoom extends React.Component {
constructor(props) {
super(props)
this.message = ""
this.contact = this.props.navigation.getParam('contact')
this.state = {
defautInputValue: "",
listMessages: [],
isLoading: true
}
}
_textInputChanged(text) {
this.message = text
}
_sendMessage() {
this.setState({ defautInputValue: " " });
sendMessage('1', this.contact.id_contact, this.message).then(() => {
this._loadMessages();
});
}
_loadMessages() {
getMessages('1', this.contact.id_contact).then((data) => {
this.setState({ listMessages: data, isLoading: false, defautInputValue: "" })
});
}
componentDidMount() {
this._loadMessages();
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={[styles.loading_container]}>
<ActivityIndicator size="large" color="orange" />
</View>
)
}
}
render() {
//console.log('Contact ID: ' + JSON.parse(this.contact))
return (
<SafeAreaView style={styles.container}>
<View style={styles.contact}>
<View style={styles.image_container}>
<Image
style={styles.image}
source={{ uri: 'https://moonchat.imedramdani.com/avatar/' + this.contact.avatar }}
></Image>
</View>
<View style={styles.username_container}>
<Text style={styles.username}>{this.contact.username}</Text>
</View>
</View>
{/* BODY */}
<View style={styles.body}>
<FlatList
ref={ref => this.flatList = ref}
onContentSizeChange={() => this.flatList.scrollToEnd({ animated: true })}
data={this.state.listMessages}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) =>
<MessageItem
message={item}
/>}
>
</FlatList>
</View>
<View style={styles.input_container}>
<TextInput
style={styles.input}
onChangeText={(text) => this._textInputChanged(text)}
onSubmitEditing={() => this._sendMessage()}
defaultValue={this.state.defautInputValue}
placeholder="Aa"
></TextInput>
</View>
{this._displayLoading()}
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1d2733',
},
contact: {
height: 50,
backgroundColor: '#1d2733',
marginTop: 0,
flexDirection: 'row',
borderBottomColor: 'grey',
borderWidth: 1
},
username_container: {
//backgroundColor: 'red',
flex: 3,
justifyContent: 'center',
alignItems: 'flex-start'
},
username: {
fontSize: 20,
color: 'white'
},
image_container: {
//backgroundColor: 'blue',
flex: 1,
justifyContent: 'center'
},
image: {
//backgroundColor: 'yellow',
height: 45,
width: 45,
marginLeft: 10,
borderRadius: 25
},
body: {
//backgroundColor: 'red',
flex: 1
},
input_container: {
height: 75,
//backgroundColor:'blue',
padding: 5
},
input: {
paddingLeft: 20,
height: 50,
backgroundColor: 'white',
borderWidth: 1,
borderRadius: 25,
borderColor: '#D5D5D5',
fontSize: 20
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
});
export default ChatRoom
Thanks!
I set up a solution that worked, but I don't know if it is a proper way.
I restored App.js
import React from 'react'
import Root from './Root'
import { Provider } from 'react-redux'
import Store from './Store/configureStore'
class App extends React.Component {
render() {
return (
<Provider store={Store}>
<Root />
</Provider>
)
}
}
export default App
I created a now component Root.js which contains notification handler
import React from 'react'
import Navigation from './Navigation/Navigation'
import messaging from '#react-native-firebase/messaging'
import { connect } from 'react-redux'
class Root extends React.Component {
requestUserPermission = async () => {
//On récupere le token
const token = await messaging().getToken();
console.log('TOKEN: ' + token)
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
}
}
handleForegroundNotification = () => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('A new FCM message arrived!', JSON.stringify(remoteMessage));
const action = { type: "RELOAD_MESSAGES", value: '1' }
this.props.dispatch(action)
});
return unsubscribe;
}
componentDidMount() {
this.requestUserPermission();
this.handleForegroundNotification();
}
render() {
return (
<Navigation />
)
}
}
const mapStateToProps = (state) => {
return {
loadyourself: state.loadyourself
}
}
export default connect(mapStateToProps)(Root)
The store is provided in App.js to let Root.js access the global state.
When a notification is received at foreground, the Root.js update a key in the global state named "loadyourself". When the state is updated, the ChatRoom.js which is connected to the store too, trigger the componentDidUpdate()
componentDidUpdate() {
if (this.props.loadyourself == "1") {
this._reloadMessages();
}
}
of course, to avoid the infinite loop, the _reloadMessages() restore default value in the global state of loadyourself key
_reloadMessages() {
const action = { type: "RELOAD_MESSAGES", value: '0' }
this.props.dispatch(action)
getMessages('1', this.contact.id_contact).then((data) => {
this.setState({ listMessages: data })
});
}
The messages are updated, the global state is re-initialized, the componentDidUpdate() does not trigger until next notification.
It works. Like I said, I don't know if there is a more proper way, I'm new in React-Native (2 weeks). I am open to other solutions

React native pure chart dynamic data problem

I'm trying to load some data to a chart using https://github.com/oksktank/react-native-pure-chart
Trying to load Json data from api but seem to get undefined only, what might be the problem?
constructor(props){
super(props);
this.state = { isLoading: true}
}
componentDidMount(){
return fetch('https://api.mockaroo.com/api/12a7ead0?count=20&key=8ba88000')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson.Weight,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
render() {
return (
<SafeAreaView style={{flex:1}}>
<PureChart data={this.state.dataSource} type='line' />
</SafeAreaView>
);
}
}
The data you are providing to PureChart is not correct.
Here is the working example: https://snack.expo.io/#msbot01/moody-orange
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import PureChart from 'react-native-pure-chart';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { isLoading: true };
}
componentDidMount() {
return fetch('https://api.mockaroo.com/api/12a7ead0?count=20&key=8ba88000')
.then(response => response.json())
.then(responseJson => {
console.log(JSON.stringify(responseJson))
var temp = [];
for (var i = 0; i < responseJson.length; i++) {
console.log(responseJson[i].Weight)
temp.push(responseJson[i].Weight)
}
this.setState({
dataSource:temp
})
})
.catch(error => {
console.error(error);
});
}
render() {
return (
<View style={styles.container}>
<PureChart data={this.state.dataSource} type="line" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});

Could not navigate to another screen when using token in React Native

I'm currently developing an app using react native, right now my issue is that i couldn't navigate to main screen after login. Below is my code.
This is App.js (EDITED)
import React from 'react';
import { Loading } from './components/common/';
import TabNavigator from './screens/TabNavigator';
import AuthNavigator from './screens/AuthNavigator';
import MainNavigator from './screens/MainNavigator';
import deviceStorage from './services/deviceStorage.js';
import { View, StyleSheet } from 'react-native';
export default class App extends React.Component {
constructor() {
super();
this.state = {
token: '',
loading: true
}
this.newJWT = this.newJWT.bind(this);
this.deleteJWT = deviceStorage.deleteJWT.bind(this);
this.loadJWT = deviceStorage.loadJWT.bind(this);
this.loadJWT();
}
state = {
isLoadingComplete: false,
};
newJWT(token){
this.setState({
token: token
});
}
render() {
if (this.state.loading) {
return (
<Loading size={'large'} />
);
} else if (!this.state.token) {
return (
<View style={styles.container}>
<AuthNavigator screenProps = {{setToken:this.newJWT}} />
</View>
);
} else if (this.state.token) {
return (
<View style={styles.container}>
<MainNavigator screenProps = {{token: this.state.token,
deleteJWT:this.deleteJWT,}} />
</View>
);
}
}
}
This is Login.js (EDITED-v2)
import React, { Component, Fragment } from 'react';
import { Text, View, StyleSheet, ImageBackground, KeyboardAvoidingView,
TouchableOpacity, TextInput, Alert } from 'react-native';
import axios from 'axios';
import deviceStorage from '../services/deviceStorage';
class Login extends Component {
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
error: '',
loading: false
};
this.loginUser = this.loginUser.bind(this);
this.onLoginFail = this.onLoginFail.bind(this);
}
loginUser() {
const { username, password, password_confirmation } = this.state;
this.setState({ error: '', loading: true });
// NOTE Post to HTTPS only in production
axios.post("http://192.168.1.201:8000/api/login",{
username: username,
password: password
})
.then((response) => {
console.log('response',response)
deviceStorage.saveKey("token", response.data.token);
console.log(response.data.token);
this.props.newJWT(response.data.token);
})
.catch((error) => {
const status = error.response.status
if (status === 401) {
this.setState({ error: 'username or password not recognised.' });
}
this.onLoginFail();
//console.log(error);
//this.onLoginFail();
});
}
onLoginFail() {
this.setState({
error: 'Login Failed',
loading: false
});
}
render() {
// other codes here
}
const styles = StyleSheet.create({
// other codes here
});
export { Login };
This is TabNavigator.js (Added)
import React from 'react';
import { Text } from 'react-native';
import { createMaterialTopTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Profile from '../screens/Profile';
const TabNavigator = createMaterialTopTabNavigator(
{
Profile: {
screen: props => <Profile {...props.screenProps} />,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-person' : 'ios-person'} //TODO change to focused
icon
size={30}
style={{ color: tintColor }}
/>
),
}
},
},
{ initialRouteName: 'Profile',
tabBarPosition: 'top',
swipeEnabled: false,
animationEnabled: true,
lazy: true,
tabBarOptions: {
showLabel: false,
showIcon: true,
activeTintColor: 'orange',
inactiveTintColor: 'orange',
style: {
backgroundColor: '#555',
},
indicatorStyle: {
color: '#orange'
}
}
}
);
const screenTitles = {
Profile: { title: 'Profiler' },
Home: { title: 'Home' },
};
TabNavigator.navigationOptions = ({ navigation }) => {
const { routeName } = navigation.state.routes[navigation.state.index];
const headerTitle = screenTitles[routeName].title;
const tabBarVisible = false;
return {
headerTitle,
tabBarVisible
};
};
export default TabNavigator;
This is my AuthLoadingScreen.js
import React from 'react';
import { View } from 'react-native';
import { Login } from '../screens/Login';
class AuthLoadingScreen extends React.Component {
constructor(props){
super(props);
this.state = {
showLogin: true
};
this.whichForm = this.whichForm.bind(this);
this.authSwitch = this.authSwitch.bind(this);
}
authSwitch() {
this.setState({
showLogin: !this.state.showLogin
});
}
whichForm() {
if(this.state.showLogin){
return(
<Login newJWT={this.props.newJWT} authSwitch={this.authSwitch} />
);
} else {
}
}
render() {
return(
<View style={styles.container}>
{this.whichForm()}
</View>
);
}
}
export default AuthLoadingScreen;
const styles = {
// style codes here
};
Lastly, this is my Profile.js
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, Alert, Platform } from
'react-native';
import { Button, Loading } from '../components/common/';
import axios from 'axios';
export default class Profile extends Component {
constructor(props){
super(props);
this.state = {
loading: true,
email: '',
name: '',
error: ''
}
}
componentDidMount(){
this.onLocationPressed();
const headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.props.token
};
axios({
method: 'GET',
url: 'http://192.168.1.201:8000/api/user',
headers: headers,
}).then((response) => {
console.log('response',response)
console.log('response2',this.props.token)
this.setState({
email: response.data.user.email,
name: response.data.user.name,
loading: false
});
}).catch((error) => {
console.log(error);
this.setState({
error: 'Error retrieving data',
loading: false
});
});
}
render() {
const { container, emailText, errorText } = styles;
const { loading, email, name, error } = this.state;
if (loading){
return(
<View style={container}>
<Loading size={'large'} />
</View>
)
} else {
return(
<View style={container}>
<View>
<Text style={emailText}>Your email: {email}</Text>
<Text style={emailText}>Your name: {name}</Text>
</View>
<Button onPress={this.props.deleteJWT}>
Log Out
</Button>
</View>
);
}
}
}
const styles = {
// style codes here
};
I've fixed the previous problem that couldn't start the app. Right now i can see the login screen, but when i pressed login, there's a yellow box that indicates some problem. I've included the screenshot below.
Lastly i've added the deviceStorage.js
deviceStorage.js
import { AsyncStorage } from 'react-native';
const deviceStorage = {
async saveKey(key, valueToSave) {
try {
await AsyncStorage.setItem(key, valueToSave);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
},
async loadJWT() {
try {
const value = await AsyncStorage.getItem('token');
if (value !== null) {
this.setState({
token: value,
loading: false
});
} else {
this.setState({
loading: false
});
}
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
},
async deleteJWT() {
try{
await AsyncStorage.removeItem('token')
.then(
() => {
this.setState({
token: ''
})
}
);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
}
};
export default deviceStorage;
Before navigate
After navigate
This is my setup. It works like a charm. Sorry if it's a bit messy. I removed some stuff for clarity and I may have missed something:
App.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { Asset, Font, Icon } from 'expo';
import { ENDPOINT, USER_TYPE } from './src/config'
import { Loading } from './src/components/common/';
import deviceStorage from './src/services/deviceStorage.js';
import TabNavigator from './src/TabNavigator';
import AuthNavigator from './src/AuthNavigator';
import MainNavigator from './src/MainNavigator';
import globalStyles from './src/globalStyles';
import './ReactotronConfig';
export default class App extends React.Component {
constructor() {
super();
this.state = {
jwt: '',
loading: true,
};
this.newJWT = this.newJWT.bind(this);
this.deleteJWT = deviceStorage.deleteJWT.bind(this);
this.loadJWT = deviceStorage.loadJWT.bind(this);
this.loadJWT();
}
state = {
isLoadingComplete: false,
};
newJWT(jwt) {
this.setState({
jwt: jwt
});
}
render() {
if (this.state.loading) {
return (
<Loading size={'large'} />
);
} else if (!this.state.jwt) {
//console.log(this.props, '<=== app.js');
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AuthNavigator screenProps={{setToken: this.newJWT }} />
</View>
);
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<MainNavigator
screenProps={{ jwt: this.state.jwt,
deleteToken: this.deleteJWT,
}}
/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
justifyContent: 'center',
},
});
AuthNavigator.js
import React from 'react';
import { createAppContainer, createBottomTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import AuthScreen from './screens/AuthScreen';
const AuthNavigator = createBottomTabNavigator(
{
Auth: (props) => {
return <AuthScreen {...props.screenProps} />;
}
},
{ initialRouteName: 'Auth',
tabBarOptions: {
showLabel: false,
activeBackgroundColor: '#eee',
}
}
);
export default createAppContainer(AuthNavigator);
MainNavigator.js
import React from 'react';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import TabNavigator from './TabNavigator';
const MainNavigator = createStackNavigator({
Main: TabNavigator },
{
initialRouteName: 'Main',
defaultNavigationOptions: {
headerTitleStyle: {
fontSize: 20,
textTransform: 'uppercase'
}
}
});
export default createAppContainer(MainNavigator);
TabNavigator.js
import React from 'react';
import { Text } from 'react-native';
import { createMaterialTopTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import IconBadge from 'react-native-icon-badge';
import ProfileScreen from './screens/ProfileScreen';
import NotificationsScreen from './screens/NotificationsScreen';
import HomeStackNavigator from './HomeStackNavigator';
import CartStackNavigator from './CartStackNavigator';
import QuotesStackNavigator from './QuotesStackNavigator';
import InitialRoute from './InitialRoute';
const TabNavigator = createMaterialTopTabNavigator(
{
Profile: {
screen: props => <ProfileScreen {...props.screenProps} />,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-person' : 'ios-person'} //TODO change to focused icon
size={30}
style={{ color: tintColor }}
/>
),
}
},
Home: HomeStackNavigator,
Quotes: QuotesStackNavigator,
Notifications: { screen: props => <NotificationsScreen {...props.screenProps} />,
navigationOptions: ({ screenProps }) => ({
tabBarIcon: ({ tintColor, focused }) => (
<IconBadge
MainElement={
<Ionicons
name={focused ? 'ios-notifications' : 'ios-notifications'}
size={30}
style={{ color: tintColor }}
/>
}
BadgeElement={
<Text style={{ color: '#FFFFFF' }}>{screenProps.unreadMessagesCount}</Text>
}
IconBadgeStyle={{ width: 15,
height: 15,
position: 'absolute',
top: 1,
left: -6,
marginLeft: 15,
backgroundColor: 'red' }}
Hidden={screenProps.unreadMessagesCount === 0}
/>
)
})
},
Cart: CartStackNavigator,
},
{ initialRouteName: 'Profile',
tabBarPosition: 'top',
swipeEnabled: false,
animationEnabled: true,
lazy: true,
tabBarOptions: {
showLabel: false,
showIcon: true,
activeTintColor: 'orange',
inactiveTintColor: 'orange',
style: {
backgroundColor: '#555',
},
indicatorStyle: {
color: '#orange'
}
}
}
);
const screenTitles = {
Profile: { title: 'Hola Maestro' },
Home: { title: 'Selecciona la Categoría' },
Quotes: { title: 'Mi Historial de Cotizaciones' },
Notifications: { title: 'Notificaciones' },
Cart: { title: 'Mi Pedido' },
};
TabNavigator.navigationOptions = ({ navigation }) => {
const { routeName } = navigation.state.routes[navigation.state.index];
const headerTitle = screenTitles[routeName].title;
const tabBarVisible = false;
return {
headerTitle,
tabBarVisible
};
};
export default TabNavigator;
Login.js
import React, { Component, Fragment } from 'react';
import { Text, View, StyleSheet, ImageBackground, KeyboardAvoidingView } from 'react-native';
import axios from 'axios';
import Ionicons from 'react-native-vector-icons/Ionicons';
//import Pusher from 'pusher-js/react-native';
import { ENDPOINT, USER_TYPE } from '../config'
import deviceStorage from '../services/deviceStorage';
import { Input, TextLink, Loading, Button } from './common';
import Header from '../components/Header';
class Login extends Component {
constructor(props){
super(props);
this.state = {
username: '',
password: '',
error: '',
loading: false
};
this.pusher = null; // variable for storing the Pusher reference
this.my_channel = null; // variable for storing the channel assigned to this user
this.loginUser = this.loginUser.bind(this);
}
loginUser() {
const { username, password, password_confirmation } = this.state;
axios.post(`${ENDPOINT}/login`, {
user: {
login: username,
password: password
}
})
.then((response) => {
deviceStorage.saveKey("id_token", response.headers.authorization);
this.props.newJWT(response.headers.authorization);
//this.setPusherData();
})
.catch((error) => {
this.onLoginFail();
});
}
onLoginFail() {
this.setState({
error: 'Usuario o contraseña incorrecta',
loading: false
});
}
}
render() {
const { username, password, error, loading } = this.state;
const { container, form, section, errorTextStyle, iconContainer, inputContainer, titleText } = styles;
return (
<View style={container}>
<Header title="¡Bienvenido Amigo Maestro!" />
<View style={form}>
<ImageBackground source={require('./cemento-login.jpg')} style={{ flex: 1, marginBottom: 30 }}>
<View style={{marginTop: 120}}>
<Text style={titleText}>INICIAR SESIÓN</Text>
<View style={section}>
<View style={iconContainer}>
<Ionicons
name={'ios-person'}
size={26}
style={{ color: '#fff', alignSelf: 'center' }}
/>
</View>
<View style={inputContainer}>
<Input
placeholder="Usuario"
value={username}
onChangeText={username => this.setState({ username })}
/>
</View>
</View>
<View style={section}>
<View style={iconContainer}>
<Ionicons
name={'ios-lock'}
size={26}
style={{ color: '#fff', alignSelf: 'center' }}
/>
</View>
<View style={inputContainer}>
<Input
secureTextEntry
placeholder="Contraseña"
value={password}
onChangeText={password => this.setState({ password })}
/>
</View>
</View>
</View>
</ImageBackground>
</View>
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={30}
>
<TextLink style={{ }} onPress={this.props.formSwitch}>
Aún no estas registrado? Regístrate
</TextLink>
<TextLink style={{ }} onPress={this.props.forgotPassword}>
Olvidaste tu contraseña?
</TextLink>
<Text style={errorTextStyle}>
{error}
</Text>
{!loading ?
<Button onPress={this.loginUser}>
Ingresar
</Button>
:
<Loading size={'large'} />}
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
form: {
flex: 0.8
},
section: {
flexDirection: 'row',
backgroundColor: '#eee',
borderRadius: 3,
marginTop: 10,
height: 40,
marginLeft: '10%',
marginRight: '10%',
},
titleText: {
color: '#fff',
alignSelf: 'center',
fontSize: 20,
marginBottom: 10
},
errorTextStyle: {
alignSelf: 'center',
fontSize: 18,
color: 'red'
},
iconContainer: {
flex: 0.1,
height: 40,
borderRadius: 3,
alignSelf: 'center',
justifyContent: 'center',
backgroundColor: 'orange',
},
inputContainer: {
flex: 0.8,
alignSelf: 'flex-start',
marginLeft: -70,
}
});
export { Login };
Inside App.js you never change loading to false so the render method never gets to any of the other conditions. Once you have received the token you need to update state and change loading.

Not getting values in props when state get changed

I am new to react-native redux , i am updating my old code which i build by using flux pattern in Redux architecture , i am learning the usage of store , thunk , reducers and Actions , Here is some of my classes which i updated :-
HomeScreenClass :-
import React, { Component } from "react";
import {
StyleSheet,
View,
StatusBar,
ActivityIndicator,
Modal,
Platform,
Image,
ScrollView,
TouchableOpacity
} from "react-native";
import { Card } from "native-base";
import NavigationDrawer from "../../component/navigationDrawerComponent/NavigationDrawer";
import CategoryProductList from "../HomeScreen/screens/CategoryProducts";
import CustomText from "../../component/customComponent/CustomText";
import ProductScreen from "./screens/ProductScreen";
import ProductDetailScreen from "./screens/ProductDetailScreen";
import PopUpMenu from "../../component/navigationDrawerComponent/PopUpMenu";
import { Font } from "expo";
import LoginScreen from "../AuthScreen/LoginScreen";
import SignUp from "../AuthScreen/SignUpScreen";
import WebApi from "../../component/webServiceComponent/WebServiceHandler";
import ForgotPassword from "../AuthScreen/ForgotPassword";
import SignUpScreen from '../AuthScreen/SignUpScreen';
import ProfileScreen from "./screens/ProfileScreen";
import ChangePassword from "../AuthScreen/ChangePassword";
import EditProfileScreen from "./screens/EditProfileScreen";
import HtmlView from "./screens/HtmlView";
import OfflineNotice from "../../component/internetCheckComponent/OfflineNotice";
import { createRouter, NavigationProvider } from "#expo/ex-navigation";
import metrics from "../../component/displaySizeComponent/metrics";
import { connect } from 'react-redux';
import { HitAllApis} from '../../actions/ApiCallActions';
var self;
export const Router = createRouter(() => ({
about: () => AboutScreen,
products: () => ProductScreen,
aboutUs: () => AboutUs,
terms: () => Terms,
rateUs: () => RateUs,
productDetails: () => ProductDetailScreen,
ProductListing: () => CategoryProductList,
feedback: () => Feedback,
htmlView: () => HtmlView,
loginScreen: () => LoginScreen,
signUpScreen: () => SignUpScreen,
profileScreen: () => ProfileScreen,
editProfileScreen: () => EditProfileScreen,
forgotPasswordScreen: () => ForgotPassword,
changePassword: () => ChangePassword
}));
class HomeScreen extends Component {
constructor(props) {
super(props);
this.state={
modalVisible: false,
loaded: false
}
self = this;
}
componentWillMount() {
console.disableYellowBox = true;
self._loadFontsAsync();
const {dispatch} = this.props;
dispatch(HitAllApis());
}
componentDidMount() {
console.log("component*****" , this.props);
}
closeModal() {
this.setState({ modalVisible: false });
}
openModal() {
if (this.state.modalVisible) {
this.setState({ modalVisible: false });
} else {
this.setState({ modalVisible: true });
}
}
_loadFontsAsync = async () => {
await Font.loadAsync({
futuraLigtBt: require("../../fonts/futuraLightBt.ttf")
});
this.setState({ loaded: true });
};
render() {
console.log("under Render ", this.props)
if (!this.props.showData || !this.state.loaded) {
return (
<View style={{ flex: 1 }}>
<Image
style={{
height: metrics.DEVICE_HEIGHT + 24,
width: metrics.DEVICE_WIDTH
}}
source={require("../../assets/splash.png")}
/>
<ActivityIndicator
color="white"
style={styles.activityIndicator}
size="small"
animating={this.props.isLoading}
/>
<OfflineNotice />
</View>
);
}
return (
<View style={styles.container}>
<NavigationProvider router={Router}>
<StatusBar barStyle="default" hidden={false} />
<NavigationDrawer
openMenu={() => this.openModal()}
disableBack={true}
magentoData={this.props.magentoData}
bannerData={this.props.bannerData}
categoryList={this.props.categoryList}
/>
</NavigationProvider>
<Modal
transparent={true}
visible={this.state.modalVisible}
animationType={"none"}
onRequestClose={() => this.closeModal()}
>
<View style={styles.modalContainer}>
<View style={styles.modalInnerContainer}>
<TouchableOpacity
style={styles.navBar}
onPress={() => this.closeModal()}
/>
<View style={{ flex: 1, backgroundColor: "white" }}>
<Card>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={styles.scrollView}>
<PopUpMenu
popUpList={this.state.popUpPageData}
closePopUp={() => this.closeModal()}
/>
</View>
</ScrollView>
</Card>
</View>
</View>
<TouchableOpacity
style={{ flex: 0.5, color: "transparent" }}
onPress={() => this.closeModal()}
/>
</View>
</Modal>
<OfflineNotice />
</View>
);
}
}
function mapStateToProps(state) {
//const { magentoData: [],showData,isLoading,popUpPageData: [],categoryList: [],bannerData: [],loaded,modalVisible} = state
return {
state
}
}
export default connect(mapStateToProps)(HomeScreen)
const styles = StyleSheet.create({
modalInnerContainer: {
flex: 0.5,
justifyContent: "center",
backgroundColor: "transparent",
flexDirection: "column"
},
container: { flex: 1, justifyContent: "center", alignItems: "center" },
activityIndicator: { position: "absolute", bottom: 20, alignSelf: "center" },
navBar: {
...Platform.select({
ios: {
height: 63
},
android: {
height: 55
}
}),
color: "transparent"
},
container: {
flex: 1,
backgroundColor: "white",
alignItems: "center",
justifyContent: "center"
},
scrollView: {
flex: 1,
borderRadius: 3,
justifyContent: "flex-start",
backgroundColor: "white",
shadowColor: "black",
shadowOpacity: 0.2,
shadowRadius: 3,
shadowOffset: {
height: 0,
width: 0
}
},
modalContainer: {
flex: 1,
flexDirection: "column",
justifyContent: "center",
backgroundColor: "transparent"
}
});
In the above class i have used Ex-natvigation , i have connected this class with Reducer.In Above when i am trying to update connection line by export default connect(mapStateToProps,{HitAllApis})(HomeScreen) , it shows me syntax error.
Here is what my Action class looks like :-
import * as types from '../types/ActionTypes'
import WebApi from "../component/webServiceComponent/WebServiceHandler";
function getCategorylisting() {
console.log('category');
return WebApi.GetApihit("/restapi/index/CategoryListing", null);
}
function getdashboard() {
console.log('das');
return WebApi.GetApihit("/restapi/index/getdashboard", null);
}
function getBanner() {
console.log('Banner');
return WebApi.GetApihit("/bannersliderapp/banner/banner", null);
}
function getStaticPages() {
return WebApi.GetApihit("/restapi/staticpages/getPages/", null);
}
export function HitAllApis (){
return function (dispatch) {
WebApi.fetchHeader().then(
function () {
Promise.all([
getCategorylisting(),
getdashboard(),
getBanner(),
getStaticPages()
]).then(function (response) {
dispatch({ type: types.Api_Response_case, data: response });
}, function (Error) {
dispatch({ type: types.Api_Request_case, data: response });
});
},
function (error) {
console.log(error);
}
);
}
}
I have requirement that i need to get data from multiple Api's , so i use promise in the Action class and grab data in one single response Object
My Store class :-
import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from '../reducers/index';
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState);
return store;
}
My Reducer class :-
import * as type from '../types/ActionTypes'
const initialState =({
magentoData: [],
showData: false,
isLoading: false,
popUpPageData: [],
categoryList: [],
bannerData: []
})
export default function DashBoardData(state = initialState, action = {}) {
switch (action.type) {
case type.Api_Request_case:
return state.isLoading = true;
case type.Api_Response_case:
state.isLoading = false;
state.showData=true;
state.categoryData = action.data[0];
state.magentoData = action.data[1];
state.bannerData = action.data[2];
state.popUpPageData = action.data[3];
// console.log('categoryData****', state.categoryData);
// console.log('magentoData****', state.magentoData);
// console.log('bannerData****', state.bannerData);
// console.log('popUpPageData****', state.popUpPageData);
return {...state};
default:
return state
}
}
And this is what i am getting inside my console.log("under Render ", this.props) :-
Object {
"dispatch": [Function anonymous],
"state": Object {
"DashBoardData": Object {
"bannerData": Array [],
"categoryList": Array [],
"isLoading": false,
"magentoData": Array [],
"popUpPageData": Array [],
"showData": false,
},
},
}
I might be doing wrong , please let me know is my approach is fine or i need to implement this in some other way , If i am doing anything wrong here Please let me know my mistake so that i can understand it more clearly.
Any Help would be greatly Appreciated!!!
Thanks
React re-renders when it determines that the old state is different from the new state. But you're modifying the old state and then copying it into the new state you return, so it thinks nothing has changed.
Your reducer should only read from the state object, it should not make modifications. For example:
export default function DashBoardData(state = initialState, action = {}) {
switch (action.type) {
case type.Api_Request_case:
return {
...state, // old state is NOT modified
isLoading: true // this is only set for the NEW state
};
case type.Api_Response_case:
return {
...state, // initially use what's in the OLD state,
isLoading: false, // then include the vales you are changing.
showData: true,
categoryData: action.data[0],
magentoData: action.data[0],
bannerData: action.data[0],
popUpPageData: action.data[0],
default:
// this REALLY means nothing has changed,
// React will not re-render
return state;
}
}

React Native Tab View _renderScene from another class

I try using Example React Native Tab View https://github.com/react-native-community/react-native-tab-view. I change on const FirstRoute to call from ListMahasiswa class, but there is error : FirstRoute(...) A Valid React element (or null) must be returned.
Main.js
import React, { PureComponent } from 'react';
import { View, StyleSheet } from 'react-native';
import { TabViewAnimated, TabBar, SceneMap } from 'react-native-tab-view';
import ListMahasiswa from './ListMahasiswa';
import InsertDataMhs from './InsertDataMhs';
import Coba from './Coba';
const FirstRoute = () => ListMahasiswa;
const SecondRoute = () => <View style={[ styles.container, { backgroundColor: '#673ab7' } ]} />;
export default class Main extends PureComponent {
state = {
index: 0,
routes: [
{ key: '1', title: 'First' },
{ key: '2', title: 'Second' },
],
};
_handleChangeTab = index => this.setState({ index });
_renderHeader = props => <TabBar {...props} />;
_renderScene = SceneMap({
'1': FirstRoute,
'2': SecondRoute,
});
render() {
return (
<TabViewAnimated
style={styles.container}
navigationState={this.state}
renderScene={this._renderScene}
renderHeader={this._renderHeader}
onRequestChangeTab={this._handleChangeTab}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
listMahasiswa.js
import React from 'react';
import {
AppRegistry,
Text,View,Button, ListView, Image, StyleSheet
} from 'react-native';
var URL="http://www.rey1024.com/api/getListMahasiswa.php";
export default class ListMahasiswa extends React.Component{
constructor(props){
super(props);
var ds = new
ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state={
dataSource: ds,
};
}
AmbilDataMahasiswa() {
fetch(URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows
(responseData),
});
}) .done();
}
render(){
this.AmbilDataMahasiswa();
return(
<View style={styles.mainContainer}>
<Text>Daftar Mahasiswa</Text>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
</View>
);
}
renderRow(record){
return(
<View style={styles.row} >
<Text>{record.nim} {record.nama}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
mainContainer :{
flex:1,
backgroundColor:'#fff'
},
row :{
borderColor: '#f1f1f1',
borderBottomWidth: 1,
flexDirection: 'row',
marginLeft: 10,
marginRight: 10,
paddingTop: 20,
},
})
Have any solution? Thank you
ListMahasiswa in the following line is not a valid React element.
const FirstRoute = () => ListMahasiswa;
Try using jsx syntax instead:
const FirstRoute = () => <ListMahasiswa />;