I have a list of team members that onPress will navigate to the next screen using react-navigation/StackNavigator. Depending on which team member is clicked, the next screen will have dynamically loaded the relevant team member information.
I have been able to achieve this by using Redux to change the state of who has been selected. However at the moment I am unable to figure how I can dispatch the action and trigger the function to navigate to the next screen.
Below is my component that holds the events in which I want to dispatch the action and navigation function. You can see the first team member 'Tim' has the navigation function. All other team members will dispatch their individual action types.
I'm fairly new to Redux I would appreciate if anyone could point me in the right direction of how to add these together.
The Repository can be found here - Github
import React, {Component} from 'react';
import {StyleSheet, View, Text, TouchableOpacity} from 'react-native';
const styles = StyleSheet.create({
pane: {
width: 300,
height: 50,
padding: 10,
backgroundColor: 'lightgray',
alignItems: 'center',
justifyContent: 'center',
margin: 3
}
});
function navTO(passBack, location) {
const { navigate } = passBack.props.navigation;
navigate(location)
}
export default class TeamList extends Component {
constructor(props) {
super(props);
}
render() {
const { passBack, location, whoSelected, Tim, Kate, Ian, Smita, Lee, Ben } = this.props;
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>{whoSelected}</Text>
<TouchableOpacity onPress={() => navTO(passBack, location)} style={styles.pane}>
<Text>Tim</Text>
</TouchableOpacity>
<TouchableOpacity onPress={Kate} style={styles.pane}>
<Text>Kate</Text>
</TouchableOpacity>
<TouchableOpacity onPress={Ian} style={styles.pane}>
<Text>Ian</Text>
</TouchableOpacity>
<TouchableOpacity onPress={Smita} style={styles.pane}>
<Text>Smita</Text>
</TouchableOpacity>
<TouchableOpacity onPress={Lee} style={styles.pane}>
<Text>Lee</Text>
</TouchableOpacity>
<TouchableOpacity onPress={Ben} style={styles.pane}>
<Text>Ben</Text>
</TouchableOpacity>
</View>
);
}
}
How about something like:
export default class TeamList extends Component {
constructor(props) {
super(props);
this.showTeamMember = this.showTeamMember.bind(this)
}
showTeamMember(teamMemberCallback) {
const { passBack, location } = this.props;
return () => {
teamMemberCallback();
navTO(passBack, location);
}
}
render() {
const { Tim, Kate, Ian, Smita, Lee, Ben } = this.props;
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>{whoSelected}</Text>
<TouchableOpacity onPress={this.showTeamMember(Tim)} style={styles.pane}>
<Text>Tim</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showTeamMember(Kate)} style={styles.pane}>
<Text>Kate</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showTeamMember(Ian)} style={styles.pane}>
<Text>Ian</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showTeamMember(Smita)} style={styles.pane}>
<Text>Smita</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showTeamMember(Lee)} style={styles.pane}>
<Text>Lee</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showTeamMember(Ben)} style={styles.pane}>
<Text>Ben</Text>
</TouchableOpacity>
</View>
);
}
}
showTeamMember creates the handler function for onPress that call both the teamMemberCallback and the navTo function.
Related
import React, { Component } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
class App extends Component {
constructor(props){
super(props)
this.state={
selected:null
}
}
handle=()=>{
this.setState({selected:1})
}
render() {
return (
<View>
<TouchableOpacity style={[styles.Btn, {backgroundColor:this.state.selected===1?"green":"white"}]} onPress={this.handle}>
<Text style={styles.BtnText}>Button 1</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.Btn} onPress={this.handle}>
<Text style={styles.BtnText}>Button 2</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.Btn} onPress={this.handle}>
<Text style={styles.BtnText}>Button 3</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
Btn: {
borderWidth: 1,
width: 100,
height: 20,
borderRadius: 8,
margin: 5,
padding: 10,
justifyContent: 'center',
flexDirection: 'row',
alignItems: 'center',
},
BtnText: {
fontSize: 15,
},
});
export default App;
Snack Link : https://snack.expo.dev/U_fX-6Tao-
I want to make it so when I click a button, the active button backgroundColor should change to "green" and text to "white" and the rest of the buttons backgroundColor and textColor should stay "red". But when I click another button then that button should become active and the previous active button should get back to its normal state.
It would be wonderful if you could also explain the logic behind it as I'm a newbie in React Native.
Thank you.
You are always setting the active button to the first one. Also, I would use an array to render the buttons. I would do something like this:
class App extends Component {
constructor(props){
super(props)
this.state = {
selected: null
}
}
handlePress = (item) => {
this.setState({ selected: item })
}
render() {
return (
<View>
{[...Array(3).keys()].map((item) => {
return (
<TouchableOpacity key={item} style={[styles.Btn, {backgroundColor: this.state.selected === item ? "green" : "white" }]} onPress={() => this.handlePress(item)}>
<Text style={styles.BtnText}>Button {item + 1}</Text>
</TouchableOpacity>
)
})}
</View>
);
}
}
I created an Themed component(OK I did not create it. It is there when I create the app with Expo).
import { useState } from 'react';
import { TouchableOpacity as DefaultTouchableOpacity } from 'react-native';
export type TouchableProps = DefaultTouchableOpacity['props'] & { activeBgColor?: string };
export function TouchableOpacity(props: TouchableProps) {
const [active, setActive] = useState(false);
const { style, activeBgColor, ...otherProps } = props;
if (activeBgColor) {
return (
<DefaultTouchableOpacity
style={[style, active ? { backgroundColor: activeBgColor } : {}]}
activeOpacity={0.8}
onPressIn={() => setActive(true)}
onPressOut={() => setActive(false)}
{...otherProps}
/>
);
}
return <DefaultTouchableOpacity style={style} activeOpacity={0.8} {...otherProps} />;
}
Then I use this TouchableOpacity everywhere.
<TouchableOpacity
style={tw`rounded-sm h-10 px-2 justify-center items-center w-1/5 bg-sky-400`}
activeBgColor={tw.color('bg-sky-600')}
>
<Text style={tw`text-white font-bold`}>a Button</Text>
</TouchableOpacity>
Oh I am writing TailwindCSS with twrnc by the way. You will love it.
See the screenshot below.
Im making an exercise timer and I need an input for seconds and minutes.
The ideal UX would 2 select lists side by side. Tapping on the input would open both lists, so different from the web where you can only focus on one at a time.
Ive made a demo here, although it would need to be in a modal or similar:
https://snack.expo.io/#jamesweblondon/intelligent-apple
import * as React from 'react';
import { Text, View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
const minutes = new Array(10).fill('').map((item, index)=>{
return index;
})
const seconds = new Array(60).fill('').map((item, index)=>{
return index;
})
const Item = ({text, isSelected, onPress}) => {
return(
<TouchableOpacity
onPress={onPress}
style={[styles.item, isSelected && styles.itemIsSelected]}>
{text}
</TouchableOpacity>
)
}
export default function App() {
const [selectedMinute, setSelectedMinute] = React.useState(1);
const [selectedSeconds, setSelectedSeconds] = React.useState(10);
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.heading}>Minutes</Text>
<ScrollView style={styles.scroll}>
{
minutes.map((item, index)=>{
return <Item
onPress={()=>setSelectedMinute(item)}
text={item}
key={item}
isSelected={selectedMinute === index}
/>
})
}
</ScrollView>
</View>
<View style={styles.row}>
<Text style={styles.heading}>Seconds</Text>
<ScrollView style={styles.scroll}>
{
seconds.map((item, index)=>{
return <Item
onPress={()=>setSelectedSeconds(item)}
text={item}
key={item}
isSelected={selectedSeconds === index}
/>
})
}
</ScrollView>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
height: 300
},
row: {
flex: 1,
},
scroll: {
height: 300
},
heading: {
fontSize: 20,
fontWeight: 'bold',
textAlign: 'center'
},
item: {
padding: 30,
backgroundColor: 'grey',
borderColor: 'white',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center'
},
itemIsSelected: {
backgroundColor: 'gold'
}
});
Is it OK to build your own inputs in React Native? This would be a a bad idea on the web as it would probably result in worse UX (especially for keyboard navigation) and a component that screen readers wouldn't know how to use.
I couldn't find a library that does quite what I need. This is close but you can only have one list open at a time. It also had a major bug with Redux making it unusable for me:
https://github.com/lawnstarter/react-native-picker-select#readme
Ive been trying to use the Picker component as I imagine it's more semantic? It works how I need on iOS but not Android (see screenshots):
I have set up a createMaterialTabNavigator with 4 screens. Each of those screens is a React Native component which accepts 3 properties (image, description, and nextPage). 'nextPage' is an onPress function and I am unable to get it to work.
As a workaround I've made separate components (Highlight1, Highlight2, etc.) and an onPress function in each to be able to navigate between them. But that is a lot of repeated code and that is what I want to avoid.
Below is the code for my TabNavigator where I assign each screen the corresponding React component.
routes.js
createMaterialTopTabNavigator(
{
Page1: {
screen: () => (
<Highlight
image={require('../components/Highlights/images/highlight1.png')}
description={'Description 1'}
nextPage={this.props.navigation.navigate('Page2')}
/>
)
},
Page2: {
screen: () => (
<Highlight
image={require('../components/Highlights/images/highlight2.png')}
description={'Description 2'}
nextPage={this.props.navigation.navigate('Page3')}
/>
)
},
Page3: {
screen: () => (
<Highlight
image={require('../components/Highlights/images/highlight3.png')}
description={'Description 3'}
nextPage={this.props.navigation.navigate('Page4')}
/>
)
},
Page4: {
screen: () => (
<Highlight
image={require('../components/Highlights/images/highlight4.png')}
description={'Description 4'}
/>
)
}
},
{
tabBarPosition: 'bottom',
defaultNavigationOptions: {
tabBarVisible: false
}
}
)
Highlight.js
import React, { Component } from 'react';
import { View, Text, Image, ImageBackground } from 'react-native';
import { NextButton } from '../Buttons';
import styles from './styles';
export default class Highlight extends Component {
render() {
return (
<ImageBackground style={styles.container}>
<Image
style={styles.image}
source={this.props.image}
resizeMode="cover"
/>
<View style={styles.textContainer}>
<Text style={styles.text1}>MYAPP</Text>
<Text style={styles.text2}>Highlights</Text>
<Text style={styles.text3}>{this.props.description}</Text>
</View>
<View style={styles.buttonContainer}>
<NextButton onPress={this.props.nextPage} />
</View>
</ImageBackground>
);
}
}
NextButton.js
import React from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import styles from './styles';
const NextButton = ({ onPress }) => (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<TouchableOpacity style={styles.nextButtonContainer} onPress={onPress}>
<Text style={{ color: 'white', fontSize: 14 }}>NEXT</Text>
</TouchableOpacity>
</View>
);
export default NextButton;
The above spits out the following error -> TypeError: undefined is not an object (evaluating '_this.props.navigation').
How can I get the above to work such that Page1 navigates to Page2, Page2 to Page3, and so on? Additionally, is there a better way to achieve what I am trying to do (which is basically to create a ViewPager consisting of 4 screens)?
The NextButton should be configured as follows:
<NextButton onPress={() => this.props.nextPage} />
const NextButton = (props) => {
const { onPress } = props;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<TouchableOpacity style={styles.nextButtonContainer} onPress={onPress}>
<Text style={{ color: 'white', fontSize: 14 }}>NEXT</Text>
</TouchableOpacity>
</View>
);
};
Please, look at the debugger and the screen for what could be a problem. However, the code is highly displayed below for your perusal.
More also, I aimed at navigating to another page based on the id of the selected content.
App.js
The App.js is where I defined my stackNavigator
import React, {Component} from 'react';
import { StyleSheet, Text, View} from 'react-native';
import Post from './components/Post';
import PostSingle from './components/PostSingle';
import { createStackNavigator, createAppContainer } from 'react-navigation';
const RootStack = createStackNavigator(
{
PostScreen: { screen: Post},
PostSingleScreen:{screen: PostSingle},
},
{
initialRouteName: "PostScreen"
}
);
const AppNavigator = createAppContainer(RootStack);
export default class App extends Component {
constructor(props) {
super(props);
};
render() {
return (
<View style={styles.container}>
<AppNavigator/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#F3F3F3',
}
});
Post.js
I have tried to delete alignItem = center. In fact I deleted my style to see if there is any blocking the screen from coming up.
import React, { Component } from 'react';
import {
ScrollView,
StyleSheet,
View,
Text,
InputText,
TouchableOpacity
} from 'react-native';
import axios from 'axios';
export default class Post extends Component{
constructor(props){
super(props);
this.state = {
posts: []
}
}
readMore = () => {
()=> this.props.navigation.navigate('PostSingleScreen');
debugger;
}
componentDidMount(){
axios.get(`http://localhost/rest_api_myblog/api/post/read.php`)
//.then(json => console.log(json.data.data[0].id))
.then(json => json.data.data.map(mydata =>(
{
title: mydata.title,
body: mydata.body,
author: mydata.author,
category_name: mydata.category_name,
id: mydata.id
}
)))
//.then(newData => console.log(newData))
.then(newData => this.setState({posts: newData}))
.catch(error => alert(error))
}
render(){
return (
<View>
<ScrollView style={styles.scrollContent}>
<View style={styles.header}>
<Text style={styles.headerText}>Gist Monger</Text>
</View>
{
this.state.posts.map((post, index) =>(
<View key={index} style={styles.container}>
<Text style={styles.display}>
Author: {post.author}
</Text>
<Text style={styles.display}>
Category: {post.category_name}
</Text>
<Text style={styles.display}>
Title: {post.title}
</Text>
<Text style={{overflow:'hidden'}}>
Id: {post.id}
</Text>
<TouchableOpacity style={styles.buttonContainer}
onPress = {() => this.readMore()}
>
<Text style={styles.buttonText}>
Read More
</Text>
</TouchableOpacity>
</View>
))
}
</ScrollView>
<View style={styles.footer}></View>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
flex: 1,
height:40,
marginTop:50,
marginBottom:10,
flexDirection: 'row',
justifyContent:'center',
},
display: {
margin: 3,
fontSize: 16
},
headerText: {
fontWeight: 'bold',
fontSize: 40,
color: '#6200EE'
},
container: {
backgroundColor:'#efefef',
padding: 20,
margin: 5,
borderRadius:20,
justifyContent: 'center',
alignItems: 'center'
},
footer: {
flex: 1,
backgroundColor:'#000',
marginBottom:50
},
buttonContainer:{
height: 30,
width: 200,
marginTop: 15,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 15,
backgroundColor:'#6200EE'
},
buttonText: {
alignContent: 'center',
color: 'white'
}
});
PostSingle.js
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text
} from 'react-native';
import axios from 'axios';
export default class Post extends Component{
constructor(props){
super(props);
}
render(){
return (
<View>
<Text>My text</Text>
</View>
);
}
}
const styles = StyleSheet.create({
});
I did not test this code, but try to add flex: 1 to your container style. the main containers/components don't stretch if you don't tell them to
const styles = StyleSheet.create({
container: {
backgroundColor: '#F3F3F3',
flex: 1,
}
});
also, to check if the components render (helps debugging where the problem is), write a console log in every component's componentDidMount. if they mount, but nothing is visible, it's most likely a CSS issue. If it wasn't, it would throw errors instead of blank screen
second issue is, when you navigate you need to have params with react-navigation. the syntax for it is like this:
this.props.navigation.navigate('PostSingleScreen', { params })
so, if you have { id: someId } in your params, in the component you navigated to you will have {this.props.navigation.state.params.id}. so basically those params are inside navigation.state.params where you navigate
Let me help you with your second question. First, it is easier as said by just specifying params in your navigation.
For instance,
readMore = (id) => {
this.props.navigation.navigate('PostSingleScreen', {id:id})
}
However, in your TouchableOpacity, onPress method i.e. onPress = {() => this.readMore(post.id)}
In your PostSingle.js
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
Button
} from 'react-native';
import axios from 'axios';
class PostSingle extends Component{
constructor(props){
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
const id = this.props.navigation.state.params.id;
axios.get(`http://localhost/rest_api_myblog/api/post/read_single.php?id=${id}`)
.then(json => json.data)
.then(newData => this.setState({posts: newData}))
.catch(error => alert(error))
}
render(){
return (
<View style={styles.container}>
<Text style={styles.display}>
{this.state.posts.title}
</Text>
<Text style={styles.display}>
{this.state.posts.author}
</Text>
<Text style={styles.display}>
{this.state.posts.category_name}
</Text>
<Text style={styles.display}>
{this.state.posts.body}
</Text>
</View>
);
}
}
I hope it helps
i would suggest using flaltist for this, and not this.state.map. this should give you the same outcome
readMore(id){
//do whatever you want with the id
this.props.navigation.navigate('PostSingleScreen',{id:id}); //or pass it as a prop
debugger;
}
renderItem = ({ item, index }) => {
return (
<View key={index} style={styles.container}>
<Text style={styles.display}>
Author: {item.author}
</Text>
<Text style={styles.display}>
Category: {item.category_name}
</Text>
<Text style={styles.display}>
Title: {item.title}
</Text>
<Text style={{overflow:'hidden'}}>
Id: {item.id}
</Text>
<TouchableOpacity style={styles.buttonContainer}
onPress = {() => this.readMore(item.id)}
>
<Text style={styles.buttonText}>
Read More
</Text>
</TouchableOpacity>
</View>
);
};
render(){
return (
<View style={{flex:1}}>
<FlatList
style={{flex:1}}
data={this.state.posts}
renderItem={this.renderItem}
numColumns={1}
keyExtractor={(item, index) => item.id} //this needs to be a unique id
ListHeaderComponent = {
<View style={styles.header}>
<Text style={styles.headerText}>Gist Monger</Text>
</View>}
/>
<View style={styles.footer}/>
</View>
);
}
If you are using react-navigation 5.x then it might be possible that you are adding CSS
alignItems: "center"
to your root file i.e. App.js or index.js I just removed it and it starts working for me.
I'm working on an app with a Side Menu and a Navigator. In the side menu there are menu-items which let's you navigate (using Navigator), and the menu-items also get styled to indicate which one is active.
When first going to a new route with navigator.push() and then going back with navigator.pop(), the menu-item corresponding to the previously active route (which is now inactive) does not get either of the styles to show that it is either inactive or active. The style of the menu item (RouteMenuItem in the full example below) is as follows
style={[
{ padding: 15, borderColor: 'firebrick', borderWidth: 1 },
(isActive
? {backgroundColor: 'green'}
: {opacity: 0.5}
)
]}
How is it possible that neither {backgroundColor: 'green'} nor {opacity: 0.5} gets applied?
The image below shows how the bug looks on Android: "Route ONE" is selected, however, the menu item for "Route TWO" does not have opacity: 0.5 as it should (it should be half transparent like the 3rd menu item).
Below is the full code for a minimal example to reproduce the bug. The component hierarchy is like this:
<Navigator renderScene={() => <SideMenu><View /></SideMenu>} />
PS: We use StyleSheet.create() in our code, but in the example below I've just defined the styles inline. It does not seem to make any difference.
import React from 'react';
import {View, Text, TouchableHighlight, Navigator, Dimensions} from 'react-native';
import SideMenu from 'react-native-side-menu';
/***************
/** Routes **/
/****************/
const ROUTES = {
ONE: () => ({ title: 'Route ONE' }),
TWO: () => ({ title: 'Route TWO' }),
THREE: () => ({ title: 'Route THREE' }),
};
/**************/
/** Main **/
/**************/
export default class Main extends React.Component {
render() {
return (
<Navigator
style={{flex: 1}}
initialRouteStack={[
ROUTES.ONE(),
]}
renderScene={(route, navigator) =>
<Scene route={route} navigator={navigator} />
}
/>
);
}
}
/***************/
/** Scene **/
/***************/
class Scene extends React.Component {
state = {
menuIsOpen: false,
}
openMenu = () => {
this.setState({ menuIsOpen: true });
}
onMenuOpenChanged = (menuIsOpen) => {
if (this.state.menuIsOpen !== menuIsOpen) {
this.setState({ menuIsOpen });
}
}
render() {
const {route, navigator} = this.props;
return (
<View style={{flex: 1}}>
<SideMenu
menu={
<Menu
route={route}
navigator={navigator}
/>
}
menuPosition="right"
bounceBackOnOverdraw={false}
onChange={this.onMenuOpenChanged}
isOpen={this.state.menuIsOpen}
openMenuOffset={Dimensions.get('window').width * 0.75}
disableGestures={false}
>
<Screen route={route} navigator={navigator} openMenu={this.openMenu} menuIsOpen={this.state.menuIsOpen} />
</SideMenu>
</View>
);
}
}
/**************/
/** Menu **/
/**************/
class Menu extends React.Component {
render() {
const {route, navigator} = this.props;
return (
<View style={{ flex: 1, backgroundColor: 'coral', paddingTop: 25 }}>
<Text>Currently at {route.title}</Text>
<RouteMenuItem forRoute={ROUTES.ONE()} route={route} navigator={navigator} />
<RouteMenuItem forRoute={ROUTES.TWO()} route={route} navigator={navigator} />
<RouteMenuItem forRoute={ROUTES.THREE()} route={route} navigator={navigator} />
</View>
);
}
}
const RouteMenuItem = ({forRoute, route, navigator}) => (
<TouchableHighlight onPress={() => navigator.push(forRoute)}>
<Text style={[ { padding: 15, borderColor: 'firebrick', borderWidth: 1 }, (forRoute.title === route.title ? {backgroundColor: 'green'} : {opacity: 0.5}) ]}>
Go to {forRoute.title} ({(forRoute.title === route.title ? 'current route' : 'NOT CURRENT ROUTE')})
</Text>
</TouchableHighlight>
);
/*****************************/
/** Screen, inside Menu **/
/*****************************/
class Screen extends React.Component {
render() {
const {route, navigator, openMenu, menuIsOpen} = this.props;
return (
<View style={{ flex: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', backgroundColor: 'peachpuff', paddingTop: 25 }}>
<HeaderButton onPress={navigator.pop}>Back</HeaderButton>
<HeaderButton onPress={openMenu}>Menu</HeaderButton>
</View>
<View style={{ flex: 1, backgroundColor: 'white' }}>
<Text style={{ margin: 50, fontSize: 50 }}>
{route.title}
</Text>
</View>
</View>
);
}
}
const HeaderButton = ({onPress, children}) => (
<TouchableHighlight underlayColor="green" onPress={onPress}>
<Text style={{ padding: 10, borderColor: 'firebrick', borderWidth: 1 }}>
{children}
</Text>
</TouchableHighlight>
);
The problem occurs becauase children of TouchableHighlight gets default opacity (1) after the TouchableHighlight is pressed. As that is a more specific problem I've asked a new question here.
In this case the bug appears when going back to a previous route, because the menu item in that instance of the Menu was pressed (moving us to a new route before we could spot the bug).