I have a React Native component that is intended to render tasks in tinder-like card stack. It gets the tasks async from a Firebase backend and renders it into a card deck called Swiper. It also has three buttons, previous next and refresh, all of which get another task and renders the card. It seems to work but only after refresh/next/previous is called, until then the card stack is empty.
However, the console.log() in ComponentDidMount() has the same task data as getMore() does. Also on initial render there is a little pagination that shows there is one task loaded, just not the actual card.
Why doesn't the card show on initial load?
import React, { PureComponent, Component } from 'react';
import FitImage from 'react-native-fit-image';
import {View, H6, Text, StyleSheet,TouchableOpacity, Image} from "react-native";
import {inject, observer} from "mobx-react/native";
import moment from "moment";
import {Task as ITask} from "../Model";
import {Avatar, Styles,Task, Firebase, Circle, BaseContainer} from "../components";
import {H3,Content, Card, CardItem, Thumbnail, Button, Icon, Left, Body, Right, Tab, Tabs, TabHeading, H1, H2} from "native-base";
import variables from "../../native-base-theme/variables/commonColor";
import Swiper from 'react-native-swiper-animated';
export default class Lists extends Component {
render(): React$Element<*> {
return <BaseContainer title="Feed" navigation={this.props.navigation}>
<Feed />
</BaseContainer>;
}
}
export class Feed extends PureComponent {
swiper = null;
prev = () => {
this.swiper.forceLeftSwipe();
this.getMore();
}
next = () => {
this.swiper.forceRightSwipe();
this.getMore();
}
constructor(props) {
super(props);
this.state = { taskList: undefined } ;
}
getMore() {
Firebase.taskRef.limitToLast(1).on("value",snapshot => {
let tasks= _.map(snapshot.val(), task => task)
.filter(task => {
;
return !task.done;
});
if (tasks.length > 0) {
tasks = tasks.map((task, key) => (
<Task {...{task, key}} />
));
}
else tasks= [];
this.setState({ taskList: tasks });
console.log(this.state.taskList);
});
}
componentDidMount() {
Firebase.taskRef.limitToLast(1).on("value",snapshot => {
let tasks= _.map(snapshot.val(), task => task);
if (tasks.length > 0) {
tasks = tasks.map((task, key) => (
<Task {...{task, key}} />
);
}
else tasks= [];
this.setState({ taskList: tasks })
console.log(this.state.taskList);
});
}
render() {
return (
<View style={{ flex: 1 }}>
<Swiper
ref={(swiper) => {
this.swiper = swiper;
}}
showPagination={true}
paginationStyle={{ container: { backgroundColor: 'transparent' } }}
paginationLeft={''}
paginationRight={''}
swiper={false}
>
{ this.state.taskList }
</Swiper>
<View style={styles.buttonContainer}>
<Button danger style={{alignSelf: 'center', height: 65, borderRadius: 100,width:65, marginRight: 20}} onPress={this.prev} >
<Icon style={{fontSize: 30}} active name='thumbs-down' />
</Button>
<Button style={{backgroundColor: '#d8d8d8', alignSelf:'center', borderRadius: 100, width: 80, alignItems:'center', justifyContent:'center', height: 80}} onPress={this.next} >
<Icon style={{fontSize: 50}} active name='md-sync' />
</Button>
<Button success style={{alignSelf: 'center', height: 65, borderRadius: 100,width:65, marginLeft: 20}} onPress={this.next} >
<Icon style={{fontSize: 30}} active name='thumbs-up' />
</Button>
</View>
</View>
);
}
}
Related
Would anyone be able to give me help regarding React Navigation with Expo? The issue is with the drawer component where the 'Hamburger' icon isn't opening or closing the component when a user presses the icon. However, it does open/close on a default swipe gesture from React Navigation. Please see below for my code:
Router:
import React from 'react';
import { NavigationContainer, DrawerActions } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { IconButton } from 'react-native-paper'
import i18n from '../i18n/i18n';
//Theme
import Theme from '../theme/theme'
//Components
import Menu from '../components/Menu'
//Import Interfaces
import { RootDrawerParamList } from '../utils/typescript/type.d';
import { IProps } from '../utils/typescript/props.d';
//Import Screens
import Screen1 from '../screens/Screen1';
import Screen2 from '../screens/Screen2';
import SettingsScreen from '../screens/Settings';
const Drawer = createDrawerNavigator<RootDrawerParamList>();
export default class Router extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
}
render() {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName='Screen1'
drawerContent={(props: any) => <Menu {...props} />}
screenOptions={({
swipeEnabled: true
})}>
<Drawer.Screen
name="Screen1"
component={Screen1}
initialParams={{ i18n: i18n, Theme: Theme }}
options={({route, navigation} : any) => ({
headerRight: () => (<IconButton icon="cog" size={24} color={Theme.colors.text} onPress={() => navigation.navigate('Settings')} />),
route: {route},
navigation: {navigation}
})}
/>
<Drawer.Screen
name="Settings"
component={SettingsScreen}
initialParams={{ i18n: i18n, Theme: Theme }}
options={({route, navigation} : any) => ({
headerTitle: i18n.t('settings', 'Settings'),
headerLeft: () => (<IconButton icon="arrow-left" color={Theme.colors.text} size={24} onPress={() => navigation.goBack()} />),
route: {route},
navigation: {navigation}
})}
/>
<Drawer.Screen
name="Screen2"
component={Screen2}
initialParams={{ i18n: i18n, Theme: Theme }}
options={({route, navigation} : any) => ({
route: {route},
navigation: {navigation}
})}
/>
</Drawer.Navigator>
</NavigationContainer>
);
}
}
Menu
import React from 'react';
import { FlatList, StyleSheet, View } from 'react-native';
import { List, Title } from 'react-native-paper';
import { getDefaultHeaderHeight } from '#react-navigation/elements';
import { DrawerItem } from '#react-navigation/drawer';
import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context';
//Import Interfaces
import { IListItem } from '../utils/typescript/types.d';
import { IPropsMenu } from '../utils/typescript/props.d';
import { IStateMenu } from '../utils/typescript/state.d';
//A function is used to pass the header height, using hooks.
function withHeightHook(Component: any){
return function WrappedComponent(props: IPropsMenu) {
/*
Returns the frame of the nearest provider. This can be used as an alternative to the Dimensions module.
*/
const frame = useSafeAreaFrame();
/*
Returns the safe area insets of the nearest provider. This allows manipulating the inset values from JavaScript. Note that insets are not updated synchronously so it might cause a slight delay for example when rotating the screen.
*/
const insets = useSafeAreaInsets();
return <Component {...props} headerHeight={getDefaultHeaderHeight(frame, false, insets.top)} />
}
}
class Menu extends React.Component<IPropsMenu, IStateMenu> {
constructor(props: IPropsMenu) {
super(props);
this.state = {
menu: [
{
name: 'screen1.name',
fallbackName: 'Screen 1',
icon: 'dice-multiple-outline',
iconFocused: 'dice-multiple',
onPress: this.props.navigation.navigate.bind(this, 'screen1')
},
{
name: 'screen2.name',
fallbackName: 'Screen 2',
icon: 'drama-masks',
iconFocused: 'drama-masks',
onPress: this.props.navigation.navigate.bind(this, 'screen2')
}
]
}
}
renderItem = (item : IListItem) => {
const { i18n } = this.props.state.routes[0].params;
return (
<DrawerItem
label={ i18n.t(item.name, item.fallbackName) }
onPress={ item.onPress ? item.onPress: () => {} }
icon={ ({ focused, color, size }) => <List.Icon color={color} style={[styles.icon, {width: size, height: size }]} icon={(focused ? item.iconFocused : item.icon) || ''} /> }
/>
);
};
render() {
const { headerHeight } = this.props;
const { menu } = this.state;
const { Theme } = this.props.state.routes[0].params;
return (
<View>
<View style={{
backgroundColor: Theme.colors.primary,
height: headerHeight ?? 0,
}}>
<View style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-end',
}}>
<View style={{
flexDirection: 'row',
alignItems: 'center',
marginBottom: 5,
marginLeft: 5
}}>
<Title style={{ color: Theme.colors.text, marginLeft: 5 }}>
Title
</Title>
</View>
</View>
</View>
<FlatList
data={menu}
keyExtractor={item => item.name}
renderItem={({item}) => this.renderItem(item)}
/>
</View>
);
};
}
export default withHeightHook(Menu);
const styles = StyleSheet.create({
icon: {
alignSelf: 'center',
margin: 0,
padding: 0,
height:20
},
logo: {
width: 24,
height: 24,
marginHorizontal: 8,
alignSelf: 'center'
},
});
The solution to my issue was to encapsulate the drawer component in a native stack component. The 'Hamburger' icon works as expected, thanks for all the help and suggestions.
// Import Screens
import DrawRouter from './DrawRouter';
const Stack = createNativeStackNavigator<RootStackParamList>();
export default (): React.ReactElement => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Main" component={DrawRouter} />
</Stack.Navigator>
</NavigationContainer>
);
};
Add the toogleDrawer() method to your onPress event on your Drawer.Navigator component:
onPress={()=> navigation.toggleDrawer()
This is what my header looks like now:
And this is what I am trying to achieve:
Code snippet:
<Stack.Navigator initialRouteName="MenuRoute">
<Stack.Screen
name={'MenuRoute'}
component={Menu}
options={({navigation, route}) => ({
headerTitle: () => (
<Text
style={{
...styles.headerTitle,
}}>
<Translatable value="Menu" />
</Text>
),
headerLeft: () => <AuthMenuPicker {...navigation} {...route} />,
headerRight: () => (
<View style={styles.row}>
<FacebookButton {...navigation} {...route}/>
<LanguagePicker />
</View>
),
headerStyle,
})}
/>
.....
.....
.....
</Stack.Navigator>
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
}
});
How can I move the Facebook Logo towards the right side (as shown in the image)?
I have tried marginLeft and paddingLeft but nothing seems to do the trick.
All help would be appreciated as I am new with this issue and with react navigation 5 in general.
UPDATE#1:
Added borderWidth:1 and borderColor: 'red' to clearly show the headerLeft area:
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
borderWidth: 1,
borderColor: 'red',
}
});
UPDATE#2 - Added component code snippets:
Code Snippet (FacebookButton component):
import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import Entypo from 'react-native-vector-icons/Entypo';
import {FACEBOOK} from '../constants';
class FacebookButton extends Component {
constructor(props) {
super(props);
}
componentDidMount() { }
render() {
return (
<>
<Button
//onPress={() => alert()}
onPress={() => {
this.props.navigate(
'FacebookMenuWebviewRoute',
{
url: FACEBOOK.FACEBOOK_PAGE,
},
);
}}
>
<Entypo
name="facebook"
size={this.props.iconSize || 25}
style={{ ...styles.icon, ...this.props.iconStyle }}
/>
</Button>
</>
);
}
}
export const styles = StyleSheet.create({
icon: {
color: 'white',
},
});
export default FacebookButton;
Code snippet (LanguagePicker component):
import React, {Component} from 'react';
import {StyleSheet} from 'react-native';
import {Menu, Button, withTheme} from 'react-native-paper';
import Fontisto from 'react-native-vector-icons/Fontisto';
import {IntlContext} from '../utility/context/Internationalization';
class LanguagePicker extends Component {
...
...
...
renderPicker() {
return (
<IntlContext.Consumer>
{(context) => {
return (
<Menu
visible={this.state.showMenu}
onDismiss={() => this.showMenu(false)}
anchor={
<Button
onPress={() => this.showMenu(true)}
style={{
...styles.menuButton,
...this.props.menuButtonStyle,
}}>
<Fontisto
name="earth"
size={this.props.iconSize || 25}
style={{...styles.icon, ...this.props.iconStyle}}
/>
</Button>
}>
{this.renderPickerItems(context)}
</Menu>
);
}}
</IntlContext.Consumer>
);
}
render() {
return <>{this.renderPicker()}</>;
}
}
export const styles = StyleSheet.create({
menuButton: {},
icon: {
color: 'white',
},
});
export default withTheme(LanguagePicker);
Thank you #GuruparanGiritharan for pointing out about the wrappers.
Solution:
Code snippet FacebookButton component:
<TouchableOpacity
style={{ justifyContent: 'center' }}
onPress={() => { ... }
>
<Entypo
name="facebook"
size={this.props.iconSize || 25}
style={{ ...styles.icon, ...this.props.iconStyle }}
/>
</TouchableOpacity>
New Header:
Explanation:
I was using Button component from react-native-paper and the component had its own fixed spacing. This caused the Facebook icon to be too spaced out.
Thus, removing the Button component and simply adding TouchableOpacity from react-native helped to reduce the space between the two icons on the header.
I have my main app with a Text, a Text Input (a component) and a button (another component):
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Tinput } from './components/Tinput.js';
import { Button } from './components/Button.js';
export default function App() {
return (
<View style={styles.container}>
<Text style={{fontSize:20, padding:20, textAlign:'center'}}>Ingrese un numero del pokémon a buscar!</Text>
<Tinput/>
<Button onPress={()=> ConsultarPokemon(/*this is where i want to insert the value from Tinput */)}>
Ingresar
</Button>
<StatusBar style="auto" />
</View>
);
}
And this is my component Tinput, which has the text input:
import React from 'react';
import { TextInput } from 'react-native';
const Tinput = () => {
const [numero, setNumero] = React.useState('');
return (
<TextInput
style={{ width:'90%', height: 50, borderColor: 'gray', borderWidth: 1, backgroundColor: '#fffff0', textAlign:'center', borderRadius: 20, borderWidth:5, margin:20}}
onChangeText={(value) => setNumero({numero: value})}
value={this.state.numero}
maxLength={20}
/>
);
}
export { Tinput };
I want to use the value from the text input on my onPress function, I tried doing this but didn't work:
<Button onPress={()=> ConsultarPokemon(Tinput.state.numero)}>
Ingresar
</Button>
Also, there's an error on my Tinput component: undefined is not an object (evaluating '_this.state.numero')
So I'm probably using state the wrong way too
You will use this.state.X only if you created classes like component classes , and here is an exemple :
class Counter extends React.Component {
constructor(props) {
super(props);
this.initialCount = props.initialCount || 0;
this.state = {
count: this.initialCount
}
}
increment() {
this.setState({ count: this.state.count + 1 })
}
reset() {
this.setState({ count: this.initialCount})
}
render() {
const { count } = this.state;
return (
<>
<button onClick={this.increment.bind(this)}>+1</button>
<button onClick={this.reset.bind(this)}>Reset</button>
<p>Count: {count}</p>
</>
);
}
}
I suggest to do not complicate things and just handle onPress inside Tinput
const Tinput = () => {
const [numero, setNumero] = React.useState('');
return (
<View>
<TextInput
style={{ width:'90%', height: 50, borderColor: 'gray', borderWidth: 1, backgroundColor: '#fffff0', textAlign:'center', borderRadius: 20, borderWidth:5, margin:20}}
onChangeText={(value) => setNumero(value)}
value={numero} // no need to use this.state.numero
maxLength={20}
/>
<Button onPress={()=> ConsultarPokemon(numero)}>
Ingresar
</Button>
</View>
);
}
I'm facing one issue I have simple and usual navigation flow with Stacknavigation and once they login they will see the Tab navigation.
In tab navigation I have chat screen which will render segments in one screen, each segment will produce a list of chat heads which is supposed to be open in the individual chat screen.
Chatscreen.js
import React, { Component } from "react";
import {
View,
Text,
StyleSheet
} from "react-native";
import AdminChat from './Chat/AdminChat';
import AcademicChat from './Chat/AcademicChat';
import ActiveChat from './Chat/ActiveChat';
import { createMaterialTopTabNavigator } from 'react-navigation';
import Icon from 'react-native-vector-icons/Ionicons';
import { Button, Container, Content, Header, Body, Left, Right, Title } from 'native-base';
class ChatScreen extends Component{
state = {
activeIndex : 0
}
segmentClicked = (index) => {
this.setState({activeIndex: index})
}
renderSection = () => {
if (this.state.activeIndex === 0) {
return <AdminChat />
} else if (this.state.activeIndex === 1) {
return <AcademicChat />
} else {
return <ActiveChat />
}
}
render(){
return (
<Container>
<Header style={{backgroundColor: '#8E44AD'}}>
<Left>
</Left>
<Body>
<Title style={{color: 'white'}}>Chat</Title>
</Body>
<Right />
</Header>
<View style={styles.container}>
<View style={{flexDirection: 'row', justifyContent: 'space-around', borderBottomWidth: 1, borderBottomColor: 'grey' }}>
<Button
transparent
onPress={()=>{this.segmentClicked(0)}}
active={this.state.activeIndex === 0}>
<Text style={[this.state.activeIndex === 0 ? { color: '#8E44AD' } : {color: 'grey'}]}>Admin</Text>
</Button>
<Button
transparent
onPress={()=>{this.segmentClicked(1)}}
active={this.state.activeIndex === 1}>
<Text style={[this.state.activeIndex === 1 ? { color: '#8E44AD' } : {color: 'grey'}]}>Academic</Text>
</Button>
<Button
transparent
onPress={()=>{this.segmentClicked(2)}}
active={this.state.activeIndex === 2}>
<Text style={[this.state.activeIndex === 2 ? { color: '#8E44AD' } : {color: 'grey'}]}>Chat</Text>
</Button>
</View>
{this.renderSection()}
</View>
</Container>
);
}
}
export default ChatScreen;
from above code, I have generated Chat Screen, which is a part of tab navigator, and in that I am loading the AdminScreen.js.
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator, StyleSheet } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
import axios from 'axios';
class AdminChat extends Component{
state = {
adminChat : [],
userid: "2980",
usertype: '1'
}
componentWillMount = async () => {
console.log(this.state.userid)
try {
let { data } = await axios
.post("https://tgesconnect.org/api/Communication_group", {
userid: this.state.userid,
group_id: '0',
is_sub_group: '0',
usertype: this.state.usertype,
})
.then(response => {
//console.log(response.data.data.group_list);
if (response.data.status === "success") {
//console.log("Success")
this.setState({
adminChat: response.data.data.group_list,
});
} else {
alert("Something went wrong");
}
});
} catch (err) {
console.log(err);
}
//console.log(this.state.adminChat.group_name[0])
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "100%",
backgroundColor: "#CED0CE",
}}
/>
);
};
render () {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.adminChat}
renderItem={({ item }) => (
<ListItem
// roundAvatar
title={item.group_name}
// subtitle={item.email}
// avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
onPress={()=>this.props.navigation.push('onescreen')}
/>
)}
keyExtractor={item => item.group_id}
ItemSeparatorComponent={this.renderSeparator}
/>
</List>
)
}
}
export default AdminChat;
const styles = StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center'
}
});
Now if I click to open a single List item and use this.props.navigation.navigate('adminSingle.js') it is not working, how can I solve it?
I am using "react-navigation": "^2.6.0"
Looks like you forgot to pass navigation as prop for AdminScreen
renderSection = () => {
if (this.state.activeIndex === 0) {
return <AdminChat navigation={this.props.navigation}/>
} else if (this.state.activeIndex === 1) {
return <AcademicChat />
} else {
return <ActiveChat />
}
}
I want to create some screen with stack and tabs navigator, but it seems not worked, it get error message like this error on virtual device...
Is this because the navigation or what?
This is my code
ConfirmActivation.js
import React, { Component } from 'react';
import { StyleSheet, Image } from 'react-native';
import { Container, Header, Content, Left, Right, Body, Text, StyleProvider,
Form, Item, Input, Label, Button, View } from 'native-base';
import { StackNavigator } from 'react-navigation';
import Icon from 'react-native-vector-icons/FontAwesome';
import getTheme from '../../../native-base-theme/components';
import material from '../../../native-base-theme/variables/material';
import Index from '../tabs/Index';
export default class sharpcs extends React.Component {
static navigationOptions = {
title: <Image source={require('../../assets/img/sharp_logo.png')} style={{width: 200, height: 50}} />,
header: null
}
render() {
return (
<AppNavigation/>
);
}
}
class ConfirmActivation extends Component{
static navigationOptions = {
title: <Image source={require('../../assets/img/sharp_logo.png')} style={{width: 200, height: 50}} />,
header: null
}
render() {
const { navigate } = this.props.navigation;
return (
<StyleProvider style={getTheme(material)}>
<Container style={styles.container} >
<Form style={styles.form} >
<Item floatingLabel>
<Label>Masukkan kode verifikasi</Label>
<Input keyboardType = 'numeric' maxLength = {13} />
</Item>
<View style={styles.buttonContainer} >
<Button
onPress={() =>
navigate('MainScreen')
} success style={{width: 125}} >
<Label style={{color: '#FFF', marginLeft: 24}} >
Lanjut
</Label>
</Button>
</View>
</Form>
</Container>
</StyleProvider>
);
}
}
const App = StackNavigator({
ThisScreen: { screen: ConfirmActivation },
MainScreen: { screen: Index }
});
const AppNavigation = () => (
<App />
);
const styles = StyleSheet.create({
form: {
flex: 2,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
},
container: {
padding: 20
},
buttonContainer: {
marginTop: 20,
flexDirection: 'row',
alignSelf: 'flex-end'
}
});
Index.js
import React, { Component } from 'react';
export default class Index extends React.Component {
render() {
return null;
}
}
The cause of the error is the title in the navigationOptions in sharpcs class. It expects a string. You had provided it an image component. Although the image appears, but this error appears when navigating. So use instead of title, headerTitle