I have a component to add todos AddTodo which works fine and update the state with my added todos and I have a component TodoItems to display the todos in <FlatList/>. I'm using React Native Tab Navigator to switch between components but I'm not sure how to send the state this.state.todos from AddTodo component to TodoItems component.
I have been researching but couldn't find a solution in Tab Navigator but there are plenty of solutions for Stack Navigator.
Component AddTodo
export default class AddTodo extends Component {
constructor(props) {
super(props);
this.state = {
todoText: null,
todos: []
}
}
onAdd = () => {
if (this.state.todoText) {
this.state.todos.push({'todoItem': this.state.todoText});
this.setState({todos: this.state.todos});
}
}
render() {
return(
<View>
<TextInput onChangeText={(text) => {
this.setState({todoText: text});
}} />
<TouchableOpacity onPress={() => {
this.onAdd;
}}>
</View>
);
}
}
Component TodoItems
export default class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {
todosList: []
}
}
render() {
return(
<View>
<FlatList
data={this.state.todosList}
renderItem={(item, index) => {
<Text>{item.todoItem}</Text>
}}
/>
</View>
);
}
}
Component Tabs
import {TabNavigator} from 'react-navigation';
import AddTodo from "./AddTodo";
import TodoItems from "./TodoItems";
var myTabs = TabNavigator(
{
'AddTodo':{screen: AddTodo,},
'TodoItems':{screen: TodoItems, },
},
{
tabBarPosition: 'top',
swipeEnabled: false,
tabBarOptions: {
labelStyle:{
fontSize: 13,
fontWeight: 'bold',
},
indicatorStyle: {
borderBottomColor: '#003E7D',
borderBottomWidth: 2,
},
style:{
backgroundColor: '#F30076',
elevation: 0,
},
},
});
export default myTabs;
Well I think you have two options:
You can use Redux which allows you to globalise your state objects so you can use them all over your app, but it can be rather complicated
https://redux.js.org/
Or you can render TodoItems from within AddTodo:
render() {
return(
<View>
<TextInput onChangeText={(text) => {
this.setState({todoText: text});
}} />
<TouchableOpacity onPress={() => {
this.onAdd;
}}>
</View>
<TodoItems data={this.state.todos} />
);
}
Then you can access that data from within TodoItems:
Hope this helps!
Related
I hope you're doing okay
I'm experiencing something weird with my react-native project, The FlatList items in some of the pages aren't displayed, even though I can see them when I console.log(json.items).
Earlier today, everything worked fine. all the pages displayed their lists as they should on my device. then I started working on a new search page on snack & I added status bar, I tested and it worked on snack before adding the new code and creating the new files in my app.
The issue I'm having now is, the list on the first page is displayed, subsequent pages after that do not show list items. including the new search page that works on snack
I'll go ahead and post my code now, the first set is for the page whose listitems are displayed correctly:
App.js
class ProfileActivity extends Component
{
// Setting up profile activity title.
static navigationOptions = ({ navigation }) =>
{
return {
title: 'Home',
headerStyle : {
backgroundColor: '#00b47a',
elevation: 0
},
headerTitleStyle: {
color: 'white'
},
cardStyle: { backgroundColor: '#fff' },
headerLeft: null,
headerRight: (
<View style={{ alignSelf: 'center', alignItems: 'center', display: 'flex', flexDirection: 'row', justifyContent: 'space-evenly'}}>
<Icon containerStyle={{ paddingRight: 10 }}
color='#fff' onPress={()=> navigation.getParam('openBottomSheet')()}
name="menu" />
<Icon containerStyle={{ paddingRight: 15 }}
color='#fff' onPress={()=> navigation.getParam('openSearchPage')()}
name="search" /></View>
)
}
};
constructor () {
super()
this.state = { toggled: false }
}
componentDidMount() {
this.props.navigation.setParams({ openBottomSheet: this.onOpenBottomSheet });
this.props.navigation.setParams({ openSearchPage: this.onOpenSearchPage });
}
onOpenBottomSheet = () => {
this.Standard.open();
}
onOpenSearchPage = () => {
this.props.navigation.navigate('sixth');
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<StatusBar
animated={true}
backgroundColor="#00b47a"
barStyle={'light-content'}
showHideTransition={'slide'}
hidden={false} />
<MarketList navigation={this.props.navigation} />
<RBSheet
ref={ref => {
this.Standard = ref;
}}
>
...
</RBSheet>
</View>
);
}
}
Market.js
export default class MarketList extends React.Component {
constructor(props) {
super(props)
this.state = {
items: '',
};
this.animateList = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.animateList, {
toValue: 1,
duration: 500,
}).start();
}
render() {
const rowStyles = [
styles.itemList,
{ opacity: this.animateList },
{
transform: [
{ scale: this.animateList },
],
},
];
fetch('https://mvmarket.xyz/nativeuserapp/home.php')
.then((response) => response.json())
.then((json) => {
this.setState({
items: json.items,
})
})
.catch((error) => {
console.error(error);
});
return (
<View style={styles.container}>
<FlatList
data={this.state.items}
renderItem={({item}) => <TouchableOpacity onPress={()=>this.props.navigation.navigate("fourth",{market:item.name})}><Animated.View style={rowStyles}><View style={styles.item}><Text style={styles.market}>{item.name}</Text><Text style={styles.location}>{item.Location}</Text></View><View style={styles.go}><Icon name="arrow-right" color="#00b47a" /></View></Animated.View></TouchableOpacity>}
/>
</View>
);
}
}
This next set is for one of the pages that don't show list items
App.js
class ProductsActivity extends Component {
static navigationOptions =
{
title: 'Products',
headerStyle : {
backgroundColor: '#00b47a',
elevation: 0
},
cardStyle: { backgroundColor: '#fff' },
headerTitleStyle: {
color: 'white'
},
};
render() {
return(
<View>
<StatusBar
animated={true}
backgroundColor="#00b47a"
barStyle={'light-content'}
showHideTransition={'slide'}
hidden={false} />
<ProductsList navigation={this.props.navigation} />
</View>
);
}
}
Products.js
export default class ProductsList extends React.Component {
constructor(props) {
super(props)
this.state = {
items: '',
};
}
render() {
fetch('https://mvmarket.xyz/nativeuserapp/products.php')
.then((response) => response.json())
.then((json) => {
this.setState({
items: json.items,
})
}).catch((error) => {
console.error(error);
console.log(error);
});
return (
<View style={styles.container}>
<FlatList
data={this.state.items}
renderItem={({item}) => <TouchableOpacity onPress={()=>this.props.navigation.navigate("fifth",{market: this.props.navigation.state.params.market, type:item.type})} style={styles.itemList}><View style={styles.item}><Text style={styles.market}>{item.type}</Text></View><View style={styles.go}><Icon name="arrow-right" color="#00b47a" /></View></TouchableOpacity>}
/>
</View>
);
}
}
I'm leaving the URL there so you can confirm yourself that the data is actually fetched. Its driving me crazy, been on it for like 4 hrs.
Thank you
I think you don't really understand lifecycle methods of a React Component. It's important to understand those concepts before jumping into code. You can check here
When you put your fetch call in render, and on then you do a setState() you are making this infinitely. This happens because you are always providing new values to items.
The ideal is to have a model layer to handle those type of calls, but this is an architecture thing, to be less complex, you can use Container/Presentation pattern.
In the Container/Presentation pattern, you have a ContainerComponent which is responsible to do requests, handle callbacks, and provide data to the Presentation component, which would be responsible to just render things.
If you don't want to use this pattern, at least put this fetch call in componentDidMount method.
Thank you all for your suggestions, duly noted and appreciated.
#Witalo-Benicio #Andris-laduzans #Drew-reese
I've fixed it, by changing
class SearchMarketActivity extends Component {
static navigationOptions = {
headerShown: false,
cardStyle: {
backgroundColor: 'white'
}
}
render() {
return(
<View>
<StatusBar
animated={true}
backgroundColor="#585858"
barStyle={'light-content'}
showHideTransition={'slide'}
hidden={false} />
<SearchMarket navigation={this.props.navigation} />
</View>
)
}
}
to
class SearchMarketActivity extends Component {
static navigationOptions = {
headerShown: false,
cardStyle: {
backgroundColor: 'white'
}
}
render() {
return(
<SearchMarket navigation={this.props.navigation} />
)
}
}
After, I added the StatusBar to the <SearchMarket /> component being imported
I'm a newbie, and I created a react native app that contains a stack navigator, so I get this error when I click on a button to navigate from one page to another.
the App.js contains the app container:
export default class App extends React.Component {
render(){
return (
<AppNavigator />
);
}
}
const AppDrawerNavigator = createDrawerNavigator({
Welcome:{
screen: WelcomeScreen
},
SeConnecter: {
screen:ConnectionScreen} ,
About : {
screen: AboutScreen
},
list :{
screen: ListMed
},
});
const screens = {
Welcome:{
screen : AppDrawerNavigator
},
SeConnecter: {
screen:AppDrawerNavigator
} ,
About : {
screen: AppDrawerNavigator
},
list :{
screen: AppDrawerNavigator
},
}
const AppStackNavigator = createStackNavigator(screens,{
defaultNavigationOptions:{
title: 'MediClic',
headerTintColor :'#fff',
headerStyle :{
backgroundColor:'#3498db',
height: 100,
},
headerTitleStyle :{
justifyContent:'center',
fontSize: 30,
fontFamily: 'serif',
fontWeight:'bold'
}
}
});
const AppNavigator= createAppContainer(AppStackNavigator);
the welcome screen contains a component search that i created
class WelcomeScreen extends React.Component {
render(){
return (
<View style={styles.container}>
<Search navigation={this.props.navigation}/>
</View>
);
}
export default WelcomeScreen
and then the component search where there is a textinput + a search button customized
class Search extends React.Component{
constructor(props){
super(props)
}
render(){
return(
<KeyboardAvoidingView style ={styles.main_container} behavior='padding'>
<Text style={styles.text}>Trouvez votre Medecin!</Text>
<TextInput style={styles.text_input} placeholder='Médecin,Centre...' />
<View style={styles.btn_ctr}>
<TouchableOpacity style={styles.btn} onPress={()=>this.navigation.navigate('ListMed') }>
<Text style={styles.btn_txt}>Rechercher</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
)
}
}
Please help me i wasted a lot of times on this, i tried passing the navigation props to it didn't work
<Search navigation={this.props.navigation}/>
as you pass this.props.navigation in navigation on Search component the function call will be - this.props.navigation
<TouchableOpacity style={styles.btn} onPress={()=>this.props.navigation.navigate('ListMed') }>
not this.navigation.navigate('ListMed')
I want to change the theme of the app(say background colors) by selecting from a list of colors. I've tried saving the state in a reducer and applying the state to a new page.
LoginView.js
import styles from "./styles";
class LoginView extends Component {
constructor(props) {
super(props);
this.state = {
primaryColor: theme.PRIMARY_COLOR
};
}
navigate = () => {
this.props.navigation.navigate("Home");
};
onPressGreen = () => {
theme.PRIMARY_COLOR = "green";
this.setState({ primaryColor: theme.PRIMARY_COLOR });
this.props.onPressGreenButton(theme.PRIMARY_COLOR);
};
onPressRed = () => {
theme.PRIMARY_COLOR = "red";
this.setState({ primaryColor: theme.PRIMARY_COLOR });
};
render() {
return (
<View
style={[styles.container, { backgroundColor: this.state.primaryColor }]}
>
<TouchableOpacity
style={styles.greenButton}
onPress={this.onPressGreen}
>
<Text>Green</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.redButton} onPress={this.onPressRed}>
<Text>Red</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.navigate}>
<Text>Home</Text>
</TouchableOpacity>
</View>
);
}
}
function mapDispatchToProps(dispatch) {
return {
onPressGreenButton: color => dispatch(loginActions.saveColor(color))
};
}
export default connect(
null,
mapDispatchToProps
)(LoginView);
HomeView.js
class HomeView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={[styles.container, { backgroundColor: this.props.color }]}>
<Text>Home</Text>
</View>
);
}
}
function mapStateToProps(state) {
return {
color: state.loginReducer.color //color saved in reducer
};
}
export default connect(
mapStateToProps,
null
)(HomeView);
In HomeView.js file instead of applying backgroundcolor in the way I did, how to include it inside styles.container. Is there any way to access function mapStateToProps inside styles.js I've imported. I've used redux to save the state permanently.
you can use redux outside of react component like styles.js
by store.getState() API in redux
but I think the best way to handle this is to create a wrapper component and apply them to it then use it everywhere you want
class MasterView extends Component {
render() {
return (
<View style={[styles.container, { backgroundColor: this.props.color }]}>
{this.props.children}
</View>
);
}
}
function mapStateToProps(state) {
return {
color: state.loginReducer.color //color saved in reducer
};
}
export default connect(
mapStateToProps,
null
)(MasterScreen);
then on each screen, you want you can use it for example
class HomeView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<MasterView>
<Text>Home</Text>
</MasterView>
);
}
}
another way is in styles.js
import store from '../store'
const containerColor=store.getState().loginReducer.color
const styles = StyleSheet.create({
container: {
backgroundColor:containerColor
},
}
export default styles
I am following the React-Navigation tutorial, and got stuck on the section titled Header interaction with its screen component. The code in the tutorial work fine in the emulator provided at snack, but I discovered that when running locally I encountered the following error:
Warning: Failed prop type: The prop 'onPress' is marked as required in 'Button', but its value is 'undefined'.
I managed to get the code working on my local machine using expo-cli by changing the onPress event assignment in navigationOptions as follows (my snack here):
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')} - as in tutorial
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
I am hoping someone might have some insight into why this is so. I checked and I am using the same version of Expo (v.32.0) locally.
App.js listing:
import React from 'react';
import { Button, Image, Platform, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
class LogoTitle extends React.Component {
render() {
return (
<Image
source={require('./spiro.png')}
style={{ width: 30, height: 30 }}
/>
);
}
}
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')}
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
),
};
};
componentWillMount() {
this.props.navigation.setParams({ increaseCount: this._increaseCount });
}
state = {
count: 0,
};
_increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Text>Count: {this.state.count}</Text>
<Button
title="Go to Details"
onPress={() => {
/* 1. Navigate to the Details route with params */
this.props.navigation.navigate('Details', {
itemId: 86,
otherParam: 'First Details',
});
}}
/>
</View>
);
}
}
class DetailsScreen extends React.Component {
static navigationOptions = ({ navigation, navigationOptions }) => {
const { params } = navigation.state;
return {
title: params ? params.otherParam : 'A Nested Details Screen',
/* These values are used instead of the shared configuration! */
headerStyle: {
backgroundColor: navigationOptions.headerTintColor,
},
headerTintColor: navigationOptions.headerStyle.backgroundColor,
};
};
render() {
/* 2. Read the params from the navigation state */
const { params } = this.props.navigation.state;
const itemId = params ? params.itemId : null;
const otherParam = params ? params.otherParam : null;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
<Button
title="Update the title"
onPress={() =>
this.props.navigation.setParams({ otherParam: 'Updated!' })
}
/>
<Button
title="Go to Details... again"
onPress={() => this.props.navigation.navigate('Details')}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
const RootStack = createStackNavigator(
{
Home: {
screen: HomeScreen,
},
Details: {
screen: DetailsScreen,
},
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
}
);
const AppContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
My guess is that this is not a fatal error, just a warning.
It will happen in any case. React Navigation docs state:
React Navigation doesn't guarantee that your screen component will be mounted before the header. Because the increaseCount param is set in componentDidMount, we may not have it available to us in navigationOptions. This usually will not be a problem because onPress for Button and Touchable components will do nothing if the callback is null. If you have your own custom component here, you should make sure it behaves as expected with null for its press handler prop.
So, navigationOptions function will be called twice:
First time before componentDidMount. Here, getParam will return undefined.
Second time after componentDidMount.
What Button is complaining about, is the first time. It does not like onPress set to undefined.
You can check this with console.log from navigationOptions:
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
console.log(navigation.getParam('increaseCount'))
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={()=>{navigation.getParam('increaseCount')()}}
//onPress={navigation.getParam('increaseCount')}
title="+1"
color={Platform.OS === 'ios' ? '#fff' : null}
/>
),
};
};
In my opinion, your code is correct, while the code from the docs simply ignores this issue.
Try instead of navigation.getParam() to use navigation.navigate()
I want to get the value of the email and passowrd in my Form.js, so that click click button can pass them a client api
1. this is my class src/components/login/Form.js
import React, { Component } from 'react';
import {
KeyboardAvoidingView,
StyleSheet,
TouchableHighlight,
Image,
} from 'react-native';
import UserInput from "./UserInput";
import usernameImg from '../../images/Iconperson.png';
import passwordImg from '../../images/Iconlock.png';
import eyeImg from '../../images/eye.png';
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
};
}
_onPress() {
console.warn("email", this.state.email)
console.warn("password", this.state.password)
}
render() {
return (
<KeyboardAvoidingView behavior='padding'
style={styles.container}>
<UserInput source={usernameImg}
onChangeText = {(email) => this.setState({email})}
keyboardType = 'email-address'
placeholder='Username'
autoCapitalize={'none'}
returnKeyType={'done'}
autoCorrect={false} />
<UserInput source={passwordImg}
onChangeText = {(password) => this.setState({password})}
placeholder='Password'
returnKeyType={'done'}
autoCapitalize={'none'}
autoCorrect={false} />
<TouchableHighlight
onPress={this._onPress}
activeOpacity={1} >
<Text>LOGIN</Text>
</TouchableHighlight>
</KeyboardAvoidingView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
}
});
2. this is my class src/components/login/UserInput.js
import React, { Component , PropTypes} from 'react';
import Dimensions from 'Dimensions';
import {
StyleSheet,
View,
TextInput,
Image,
} from 'react-native';
const DEVICE_WIDTH = Dimensions.get('window').width;
const DEVICE_HEIGHT = Dimensions.get('window').height;
export default class UserInput extends Component {
render() {
return (
<View style={styles.inputWrapper}>
<Image source={this.props.source}
style={styles.inlineImg} />
<TextInput style={styles.input}
onChangeText = {(this.props.ChangeText) =>
this.setState({field})}
placeholder={this.props.placeholder}
secureTextEntry={this.props.secureTextEntry}
autoCorrect={this.props.autoCorrect}
autoCapitalize={this.props.autoCapitalize}
returnKeyType={this.props.returnKeyType}
placeholderTextColor='white'
keyboardType ={this.props.keyboardType}
underlineColorAndroid='transparent' />
</View>
);
}
}
UserInput.propTypes = {
ChangeText : PropTypes.string,
source: PropTypes.number.isRequired,
placeholder: PropTypes.string.isRequired,
keyboardType : PropTypes.string,
secureTextEntry: PropTypes.bool,
autoCorrect: PropTypes.bool,
autoCapitalize: PropTypes.string,
returnKeyType: PropTypes.string,
};
const styles = StyleSheet.create({
input: {
backgroundColor: 'rgba(255, 255, 255, 0.2)',
width: DEVICE_WIDTH - 40,
height: 40,
marginHorizontal: 20,
paddingLeft: 45,
borderRadius: 20,
color: '#ffffff',
},
inputWrapper: {
flex: 1,
},
inlineImg: {
position: 'absolute',
zIndex: 99,
width: 22,
height: 22,
left: 35,
top: 9,
},
});
As you are typing into the TextInput I see you are trying to change the state in the Form component. For this to happen correctly, setState needs to be called from the Form component. There are a couple functions currently being passed down through props:
(email) => this.setState({email})
// and
(password) => this.setState({password})
Looking at how those are being used in your UserInput component, whenever a new character is added to that text box, invoke the function above. so when this.setState() is called, it is saying UserInput.setState(). Since we want to change the state in Form we have to bind those functions to the parent component.
Instead of passing the functions directly to props, let's add some user methods:
export default class Form extends Component {
constructor(props) {}
_onChangeEmail(email) {
this.setState(Object.assign({}, state, { email })); // good practice for immutability
}
_onChangePassword(password) {
this.setState(Object.assign({}, state, { password })); // good practice for immutability
}
render() {}
}
Next, we need to bind these class methods to itself. That way no matter where these are called, this will always point to the Form component. This is most commonly done in the constructor:
export default class Form extends Component {
constructor(props) {
super(props)
this.state = {}
// this is where we do the binding
this._onChangeEmail = this._onChangeEmail.bind(this)
this._onChangePassword = this._onChangePassword.bind(this)
}
_onChangeEmail(email) { /* code */}
_onChangePassword(password) { /* code */}
render() {}
}
Now pass these down to the UserInput component through props:
// Form.js
render() {
return (
<KeyboardAvoidingView>
<UserInput onChangeText={this._onChangeEmail} />
<UserInput onChangeText={this._onChangePassword} />
</KeyboardAvoidingView>
)
}
These methods should now be used when the user inputs text:
export default class UserInput extends Component {
render() {
return (
<View>
<Image />
<TextInput onChangeText = {this.props.onChangeText} />
</View>
);
}
}
Bonus: You also should add a property 'value' to the text input:
export default class UserInput extends Component {
render() {
return (
<View>
<Image />
<TextInput
onChangeText={this.props.onChangeText} />
value={this.props.textValue}
</View>
);
}
}
And make sure it is passed down from the parent component:
// Form.js
render() {
return (
<KeyboardAvoidingView>
<UserInput
onChangeText={this._onChangeEmail}
textValue={this.state.email}/>
<UserInput
onChangeText={this._onChangePassword}
textValue={this.state.password}/>
</KeyboardAvoidingView>
)
}
The value of TextInput may be retrieved through its _lastNativeTextproperty.
this.refs.myTextInput._lastNativeText
See this answer