React Native: Render different component based on button press - reactjs

I am new to React/React Native and am currently building a screen that consists of a section consisting of 3 buttons and a space below to render different components depending on which tab button is pressed. The tabs are add, remove, and history.
Basically the intent is to recreate something like the tabs component from React Bootstrap, but for React Native. I have seen the React Navigation tabs component, but I do not want the tabs to navigate to a different screen, just render a different component depending on which is clicked and have it fade into the section below the buttons.
Below is a rough idea of how I am thinking of approaching the problem.
const ChoiceContainer = props => {
const {children} = props;
render(<View>{children}</View>);
};
const AddEntry = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 1</Text>
</View>
</Card>
</ChoiceContainer>
);
const RemoveEntry = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 2</Text>
</View>
</Card>
</ChoiceContainer>
);
const EntryHistory = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 3</Text>
</View>
</Card>
</ChoiceContainer>
);
export default class EntryTabs extends Component {
showAdd = () => {
this.setState({sceneType: 'add'});
};
showRemove = () => {
this.setState({sceneType: 'receive'});
};
showHistory = () => {
this.setState({sceneType: 'history'});
};
renderScene = type => {
if (type === 'add') {
return <AddEntry />;
}
if (type === 'remove') {
return <RemoveEntrty />;
}
if (type === 'history') {
return <EntryHistory />;
}
};
render() {
const {sceneType} = this.state;
render(
<SafeAreaView>
<View style={{flex: 1}}>
return (
<EntryCard
/>
);
})}
<View style={{flex:1}}>
<View>
<TouchableOpacity onPress={this.showAdd}>
<Text> Add </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showRemove}>
<Text> Remove </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showHistory}>
<Text> History </Text>
</TouchableOpacity>
</View>
<View>{this.renderScene(sceneType)}</View>
</View>,
</View>
</SafeAreaView>
);
}
}
Any help/guidance would huge hugely appreciated. Thanks!

You have not defined the state, And you have to use map to show the entries.
The working version of you code should be as below.
const value1 = 1,
value2 = 2,
value3 = 3;
const entries = [
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
];
const ChoiceContainer = (props) => {
const { children } = props;
return <View>{children}</View>;
};
const AddEntry = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Add Entry</Text>
</View>
</Card>
<TextInput value={entries.key1} />
</ChoiceContainer>
);
const RemoveEntry = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Remove Entry</Text>
</View>
</Card>
<TextInput value={entries.key2} />
</ChoiceContainer>
);
const EntryHistory = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Entry History</Text>
</View>
</Card>
<TextInput value={entries.key3} />
</ChoiceContainer>
);
class EntryTabs extends React.Component {
state = {
sceneType: 'add',
};
showAdd = () => {
this.setState({ sceneType: 'add' });
};
showRemove = () => {
this.setState({ sceneType: 'receive' });
};
showHistory = () => {
this.setState({ sceneType: 'history' });
};
renderScene = (type) => {
if (type === 'add') {
return <AddEntry />;
}
if (type === 'remove') {
return <RemoveEntry />;
}
if (type === 'history') {
return <EntryHistory />;
}
};
render() {
const { sceneType } = this.state;
return (
<SafeAreaView>
<View style={{ flex: 1 }}>
{entries.map((item) => {
return (
<EntryHistory
baseAsset={item.key1}
logo={item.key2}
screen={item.key3}
/>
);
})}
<View style={{ flex: 1 }}>
<View>
<TouchableOpacity onPress={this.showAdd}>
<Text> Add </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showRemove}>
<Text> Remove </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showHistory}>
<Text> History </Text>
</TouchableOpacity>
</View>
<View>{this.renderScene(sceneType)}</View>
</View>
,
</View>
</SafeAreaView>
);
}
}
function CustomText(props) {
const [showMore, setShowMore] = React.useState(false);
const [lines, setLines] = React.useState(props.numberOfLines);
const onTextLayout = (e) => {
setShowMore(
e.nativeEvent.lines.length > props.numberOfLines && lines !== 0
);
};
return (
<View>
<Text numberOfLines={lines} onTextLayout={onTextLayout}>
{props.children}
</Text>
&&{' '}
{showMore && (
<Button
title="Show More"
onPress={() => {
setLines(0);
setShowMore(false);
}}
/>
)}
{!showMore && (
<Button
title="Hide More"
onPress={() => {
setLines(props.numberOfLines);
}}
/>
)}
</View>
);
}

Related

React Native - Passing navigation into a child component

I have a screen with the following:
function Intereset ({ navigation }) {
function ReturnMyFunction () {
if (!var.length) {
return ( <NoILikes /> )
} else {
return (
<FlatList
data={Ilike}
keyExtractor={item => item._id}
ItemSeparatorComponent={() => <Divider />}
renderItem={UserRow}
/>
)
}
}
return ( <ReturnILikeOrNoILike /> )
}
export default Interest
Here is my UserRow component below:
const UserRow = ({ item, navigation }) => (
<TouchableOpacity onPress={() => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })}>
<View style={styles.row}>
<Image style={styles.avatar}
resizeMode={"cover"}
source={{ uri: item.likeToProfileImage }}
/>
<View style={styles.textContainer}>
<Text style={styles.name}>{item.likeToName}, <Text>{item.likeToAge}</Text></Text>
</View>
<Text style={styles.viewProfileText}>View Profile</Text>
<AntDesign name="right" size={14} color="black" />
</View>
</TouchableOpacity>
)
When I click on the UserRow to navigate I get the following issue.
I'm using useNavigation to redirect screen in stack:
import { useNavigation } from "#react-navigation/native";
const UserRow = ({ item }) => {
const navigation = useNavigation()
const onGoToProfileDetailScreenSingle = () => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })
return (
<TouchableOpacity onPress={onGoToProfileDetailScreenSingle}>
...
</TouchableOpacity>
);
}
const renderItem = ({ item }) => <UserRow item={item} />
return (
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>
)
Use onPress handler instead and perform navigation on the screen.
const UserRow = ({ item, onPress }) => (
<TouchableOpacity onPress={onPress}>
...
</TouchableOpacity>
);
const renderItem = ({ item }) => {
return (
<UserRow
item={item}
onPress={() => navigation.navigate(...)}
/>
);
};
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>

Mobx store do not update with observer

I have a simple react native app with two screens.
First screen is the list, where you see your selected groups, and you can remove them, by clicking on trash icon:
export const Settings: NavioScreen = observer(({ }) => {
...
return (
<View flex bg-bgColor padding-20>
<FlashList
contentInsetAdjustmentBehavior="always"
data={toJS(ui.savedGroups)}
renderItem={({item}) => <ListItem item={item} />}
estimatedItemSize={20}
/>
</View>
);
});
};
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<TouchableOpacity onPress={() => ui.deleteGroup(item)}>
<Icon name={'trash'}/>
</TouchableOpacity>
</View>
);
};
The second screen is also the list, where you can add and remove the subjects from the list:
export const Playground: NavioScreen = observer(() => {
...
const groupsToShow =
ui.search && ui.search.length > 0
? ui.groups.filter((p) =>
p.name.toLowerCase().includes(ui.search.toLowerCase())
)
: ui.groups;
return (
<View >
<FlashList
data={toJS(groupsToShow)}
renderItem={({item}) => <ListItem item={item} />}
/>
</View>
);
});
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<View>
<If
_={ui.isGroupSaved(item)}
_then={
<TouchableOpacity onPress={(e) => {ui.deleteGroup(item)}}>
<AntDesign name="heart" size={20} color={Colors.primary} />
</TouchableOpacity>
}
_else={
<TouchableOpacity onPress={(e) => {ui.addGroup(item)}}>
<AntDesign name="hearto" size={20} color={Colors.primary} />
</TouchableOpacity>
}
/>
</View>
</View>
);
};
And now when I remove the group from the first list, the heart icon do not update on the second list. But it should, because there is an if statement on second list, that checks if the group is saved. And if it is not, the heart should have the name="hearto"
I have tried to use the state instead mobx library but it does not also help.
Here is my store written with mobx:
export class UIStore implements IStore {
savedGroups = [];
constructor() {
makeAutoObservable(this);
makePersistable(this, {
name: UIStore.name,
properties: ['savedGroups'],
});
}
addGroup = (group: any) => {
if (true === this.isGroupSaved(group)) {
return;
}
this.savedGroups.push(group);
}
isGroupSaved = (group: any) => {
return -1 !== this.savedGroups.findIndex(g => g.id === group.id);
}
deleteGroup = (groupToDelete: any) => {
this.savedGroups = this.savedGroups.filter((group) => group.id !== groupToDelete.id);
}
}

Check visibility of item at the screen

I have FlastList with data. I need to check if item from the Flastlist is seen. So if i do not see data at my screen i do not have to do anything, but if i see i have to console.log data info. And when i am scrolling i have to console.log data that is visibile. I am trying to use onViewableItemsChanged with viewabilityConfig, but when i console.log data, it returns all data from FlastList but not data that is seen. Help me please.
I will be very thankfull!
_onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems.map(item => item.item.text));
};
_viewabilityConfig = {
viewAreaCoveragePercentThreshold: 100
};
//....
<FlatList
data={this.state.postData}
initialNumToRender={0}
ItemSeparatorComponent = {this.FlatListItemSeparator}
renderItem={({item}) => {
return (
<View style={{paddingTop: 15}}
ref={ (divElement) => { this.divElement = divElement } }
>
// data
)}
}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={this._viewabilityConfig}
keyExtractor={item => item.id}
/>
In your comment you specified that the FlatList is inside a view and scrollview. I tried to reproduce it this way:
<View style={styles.container}>
<ScrollView style={styles.scrollView}>
<FlatList
data={DATA}
initialNumToRender={0}
renderItem={renderItem}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
keyExtractor={item => item.id}
/>
</ScrollView>
</View>
And indeed, console.log shows all data in this case. When I removed ScrollView so that the FlatList is inside the View element (with flex:1) then it works correctly - console.log shows only visible elements. My code:
const DATA = [
{
id: '1',
title: 'First Item',
},
// ... more elements
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const renderItem = ({item}) => {
return (
<View style={{paddingTop: 15}}
ref={ (divElement) => { this.divElement = divElement } }
>
<Item title={item.title} />
</View>
)
}
const viewabilityConfig = {
viewAreaCoveragePercentThreshold: 100
};
const onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems.map(item => item.item.title));
};
export default function App() {
return (
<View style={styles.container}>
<FlatList
data={DATA}
initialNumToRender={0}
renderItem={renderItem}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
keyExtractor={item => item.id}
/>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});

Invalid use of hooks when calling component with onPress

I'm trying to work with modals when I click on a button from the header.
Say I have this component List, and List is using custom navigation options:
import { CustomModal } from './components/Modal';
const List = (props) => {
const [enteredUrl, setEnteredUrl] = useState('');
const urlInputHandler = (enteredUrl) => {
setEnteredUrl(enteredUrl);
};
const addUrlHander = () => {
console.log(enteredUrl);
}
return (
<View></View>
);
};
List.navigationOptions = (navData) => {
return {
headerTitle: 'Workouts',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Add'
iconName='md-add'
onPress={() => {
CustomModal(); //here is the modal
}}
/>
</HeaderButtons>
),
headerBackTitle: null
};
};
My Modal component has this:
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return (
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
);
}
But it is giving me the invalid hook error. Why is it that my onPress in my navigationOptions giving me this? Am I doing this wrong?
onPress is a callback, you can't put components in it. Probably what you want is something like this:
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<CustomModal/>
</HeaderButtons>
and the modal looks like
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return modalVisible?(
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
):(
<Item
title='Add'
iconName='md-add'
onPress={() => setModalVisible(!modalVisible)}
/>
);
}

Show View when scroll up Scrollview

How to limit the quantity of View inside of a scrollview.
My component take too much time to render, because the map function renders too many views. I need to show only 10 views, and when scroll up, renders more 10.
I'm using react native, hooks and typescript.
First of all, if you have a large number of list data don't use scrollview. Because initially, it loads all the data to scrollview component & it costs performance as well.
Use flatlist in react-native instead of scrollview & you can limit the number of items to render in the initially using initialNumToRender. When you reach the end of the scroll position you can call onEndReached method to load more data.
A sample will like this
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
class FlatListDemo extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
};
handleRefresh = () => {
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
this.makeRemoteRequest();
}
);
};
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
this.makeRemoteRequest();
}
);
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return <SearchBar placeholder="Type Here..." lightTheme round />;
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
keyExtractor={item => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
</List>
);
}
}
export default FlatListDemo;
Check this for more informations.
I changed to Flatlist! But initialNumToRender is not working as expected.
The flatlist is rendering all 150 transactions, not only 15, and i have no idea what to do.
I'm running .map() from another array with all others transactions to create newMonths with only those transactions that i want to use data={newMonths}.
let newMonths = [];
const createArrayMonth = histInfos.map(function (info) {
if (info.created_at.slice(5, 7) === month) {
newMonths = [info].concat(newMonths);
}
});
them, i created my component:
function Item({ value }: { value: any }) {
let createdDay = value.item.created_at.slice(8, 10);
let createdMonth = value.item.created_at.slice(5, 7);
let createdYear = value.item.created_at.slice(2, 4);
function dateSelected() {
if (
month === createdMonth &&
year === createdYear &&
(day === '00' || day == createdDay)
) {
console.log('foi dateSelected');
const [on, setOn] = useState(false);
const Details = (on: any) => {
if (on === true) {
return (
<View style={styles.infos}>
<Text style={styles.TextInfos}>
{' '}
CPF/CNPJ: {value.item.cpf_cnpj}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Criado em: {value.item.created_at}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Endereço da carteira: {value.item.address}{' '}
</Text>
<Text style={styles.TextInfos}> Valor: {value.item.amount}BTC </Text>
</View>
);
} else {
return <View />;
}
};
return (
<View>
<TouchableOpacity
style={styles.card}
onPress={() => setOn(oldState => !oldState)}>
<View style={styles.dateStyleView}>
<Text style={styles.dateStyle}>{createdDay}</Text>
</View>
<View style={styles.left}>
<Text style={styles.title}>Venda rápida</Text>
<Text style={styles.semiTitle}>
{
{
0: 'Pendente',
1: 'Aguardando conclusão',
2: 'Cancelado',
100: 'Completo',
}[value.item.status]
}
</Text>
</View>
<View style={styles.right2}>
<Text style={styles.price}>R$ {value.item.requested_value}</Text>
</View>
</TouchableOpacity>
<View>{Details(on)}</View>
</View>
);
}
}
return dateSelected();}
and i call it here
return (
<ScrollView>
...
<View style={styles.center}>
...
<View style={styles.middle2}>
...
<FlatList
extraData={[refresh, newMonths]}
data={newMonths}
renderItem={(item: any) => <Item value={item} />}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={15}
/>
</View>
</View>
</ScrollView>);}
The scroll bar in the right, start to increase until renders all transactions from the data:
App scroll bar

Resources