I am new in React and trying to build dynamic form. It seems to work fine. The problem is when i type the field values, they are shown in screen, but the value property of Textinput remain null. I tried to explore all options, and it came down to async of setState. Since i am new i do not know how to make a call back function which can populate the value property of the dynamic form fields.
I have not inlcuded all the code, just what i thought would be relevant to avoid burden.
thanks
sal
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
InputArray: [],
view_data: {
id: 0,
data: null
},
step: 0,
TotalItem: [],
item:
{
id: 0,
Email: null,
Password: null,
Address: null
}
}
};
///onCHANGE FUNCTIONS
EnterValue1 = (e) => {
e.persist();
let item = { ...this.state.item };
item.Email= e.target.value;
this.setState({ item: item });
EnterValue2 = (e) => {
e.persist();
let item = { ...this.state.item };
item.Password = e.target.value;
this.setState({ item: item });
EnterValue3 = (e) => {
e.persist();
let item = { ...this.state.item };
item.Address = e.target.value;
this.setState({ item: item });
//Dynamic form
Inputs = () => {
return (
<View >
<TextInput
placeholder="enter email"
onBlur={this.focusHandler}
value={this.state.item.Email}
onChange={this.EnterValue1}
style={{ borderWidth: 2, borderColor: 'skyblue', margin: 20 }}
/>
<TextInput
placeholder="Password"
onBlur={this.focusHandler}
value={this.state.item.Password}
onChange={this.EnterValue2}
style={{ borderWidth: 2, borderColor: 'skyblue', margin: 20 }}
/>
<TextInput
placeholder="Address"
onBlur={this.focusHandler}
value={this.state.item.Address}
onChange={this.EnterValue3}
style={{ borderWidth: 2, borderColor: 'skyblue', margin: 20 }}
/>
</View>
)
};
// Render Method
render() {
return (
<View style={{ flex: 1, marginTop: 20 }}>
<ScrollView style={styles.scrollView} keyboardShouldPersistTaps='always' >
{this.state.InputArray.map((item, index) => (
//using highlight because it doenst pass on its effect to children, opacity does
<View key={index} onPress={() => this.viewPress(index)}>
<TouchableOpacity onPress={() => this.viewPress(index)}>
{item.data}
{this.state.step === 0 ?
<View style={styles.container}>
<View style={{ flex: 1 }} >
<Button type='button' style={{ flex: 1, backgroundColor: 'red' }} title="add" onPress={this.add} />
</View>
</View>
:
<View style={styles.container}>
<View style={{ flex: 1 }} >
<Button type='submit' style={{ flex: 1, backgroundColor: 'red' }} title="add" onPress={this.add} />
</View>
<View style={{ flex: 1 }}>
<Button type='button' style={{ flex: 1, backgroundColor: 'red' }} title="Remove" onPress={() => this.remove(index)} />
</View>
</View>
}
</TouchableOpacity>
</View>
))
}
</ScrollView>
<View style={styles.container}>
<View style={{ flex: 1 }}>
<Button type='submit' style={{ flex: 1, backgroundColor: 'blue' }} title="submit" onPress={this.submit} />
</View>
</View>
</View>
);
}
}
EnterValue3 = (e) => {
e.persist();
let item = { };
this.setState({ item: {...this.state.item, address: e.target.value });
}
Replace all your function with spread operator rather than directly assigning into the object.
Try this and check
EnterValue1 = (e) => {
e.persist();
this.setState({
item: {
...this.state.item,
Email: e.target.value,
}
});
}
Note: Your whole code may help much to debug your issue
Just in case for someone interested. This may not be the best solution. As this is my first project in react native.
Although i was not able to get the values prop using this.state, and they remained null. For my dynamic form, i made a function containing my Views/textinput with an index argument, provided by my map function(which iterates over an array that has length equal to number of forms added). I used onChageText method and in setState used a callback to save the typed values in an object with an id, that needs to be equivalent to the index of my dynamics mapped form. Using index of object of arrays, values=this.state.object[index],values, is saved in dynamic form.
It still did not populate the values prop, but it sure did maintain the typed content in the front end of the previous form when i add new form.
Related
I have this code, where on ChangeText in react native, i am inserting multiple field values in the state, item. For some reason, the output in the console log shows alphabetical order. Since the logic is, that on ChangText, the function EnterValue inserts the value in item state. it finds the property name and then matches that to the text value, I think something gets wrong along the way. Please see below the code
import { StatusBar } from "expo-status-bar";
import React from "react";
import { StyleSheet, Text, View, Button, TextInput } from "react-native";
class App extends React.Component {
constructor() {
super();
this.state = {
step: 1,
TotalItem: [],
item: {
Brand: null,
Quantity: null,
Instructions: null,
},
};
}
// remember this logig since take names, it adds property values in alphabetical order, and thus change the order
EnterValue = (name) => {
return (text) => {
this.setState((prevState) => ({
item: { ...prevState.item, [name]: text },
}));
};
};
submit = (e) => {
e.preventDefault(e);
const { step } = this.state;
this.setState({
step: step + 1,
});
this.setState(
{
TotalItem: [...this.state.TotalItem, this.state.item],
},
() => {
console.log("updated state", this.state.TotalItem);
}
);
};
render() {
return (
<View style={{ flex: 1, margin: 20 }}>
<TextInput
placeholder="enter name"
onChangeText={this.EnterValue("Brand")}
style={{ borderWidth: 2, borderColor: "skyblue", margin: 20 }}
/>
<TextInput
placeholder="Quantity"
onChangeText={this.EnterValue("Quantity")}
style={{ borderWidth: 2, borderColor: "skyblue", margin: 20 }}
/>
<TextInput
placeholder="Instructions"
onChangeText={this.EnterValue("Instructions")}
style={{ borderWidth: 2, borderColor: "skyblue", margin: 20 }}
/>
<View style={styles.container}>
<View style={{ flex: 1 }}>
<Button
style={{ flex: 1, backgroundColor: "red" }}
title="add"
onPress={this.add}
/>
</View>
<View style={{ flex: 1 }}>
<Button
style={{ flex: 1, backgroundColor: "blue" }}
title="submit"
onPress={this.submit}
/>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
});
export default App;
Change
onChangeText={this.EnterValue("Quantity")}
onChangeText={this.EnterValue("Brand")}
onChangeText={this.EnterValue("Instructions ")}
To
onChangeText={() => this.EnterValue("Quantity")}
onChangeText={() => this.EnterValue("Brand")}
onChangeText={() => this.EnterValue("Instructions ")}
At my example, the function “getData” loading my data, but after the loading, I try to print and show the total sum of the objects that came from JSON in a footer at the bottom of the screen.
and I don't really know how to do it.
I don't understand how to solve this issue coz I have tried many ways.
This is my example:
export default class MainScreen extends Component {
constructor(props) {
super(props);
this.state = { data: [] };
}
getData = () => {
this.setState({ isLoading: true })
axios.get("https://rallycoding.herokuapp.com/api/music_albums")
.then(res => {
this.setState({
isLoading: false,
data: res.data
});
console.log(res.data);
});
}
componentDidMount() {
this.props.navigation.setParams({getData: this.getData}); //Here I set the function to parameter
this.getData()
}
renderItem(item) {
const { title, artist} = item.item;
return (
<TouchableOpacity
onPress={() => this.props.navigation.navigate("Settings")}
>
<Card
containerStyle={{
borderColor: "black",
padding: 20,
height: 100,
backgroundColor: "#e6e6ff",
borderBottomEndRadius: 10,
borderTopRightRadius: 10,
borderBottomStartRadius: 10,
}}
>
<View
style={{
paddingVertical: 15,
paddingHorizontal: 10,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Icon name="chevron-right" size={30} color={"grey"} justifyContent={"space-between"} />
<Text style={styles.name}>
{title+ " " + artist}
</Text>
{/* <Text style={styles.vertical} numberOfLines={2}></Text> */}
</View>
</Card>
</TouchableOpacity>
);
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 230 }}>
<Text
style={{ alignSelf: "center", fontWeight: "bold", fontSize: 20 }}
>
loading data...
</Text>
<ActivityIndicator size={'large'} color={'#08cbfc'} />
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem.bind(this)}
keyExtractor={item => item.id}
/>
</View>
);
}
}
/////////////////////////////////////////////////////////
MainScreen.navigationOptions = navData => {
return {
headerTitle: 'melon',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title=**"sync button"**
iconName={Platform.OS === "android" ? "md-sync" : "ios-sync"}
onPress={() => {
navData.navigation.navigate("getData");
}}
/>
</HeaderButtons>
)
};
};
If type of data is array you can get total number of elements by this.state.data.length
If type of data is object you can get total number of elements by Object.keys(data).length
In my React-Native project, I want to use Modal inside render. I have declared one state variable like below-
this.state = {
ModalVisibleStatus: false
};
For showing the Modal I have declared a function-
ShowModalFunction(visible) {
this.setState({ModalVisibleStatus: visible});
}
And inside the render function I have just write show a Modal like below on a Button Press-
<Modal
transparent={false}
animationType={"slide"}
visible={this.state.ModalVisibleStatus}
onRequestClose={ () => { this.ShowModalFunction(!this.state.ModalVisibleStatus)} } >
<View style={{ flex:1, justifyContent: 'center', alignItems: 'center' }}>
<View style={styles.ModalInsideView}>
{/* Put All Your Components Here, Which You Want To Show Inside The Modal. */}
<Text style={styles.TextStyle}>Text Component With Some Sample Text In Modal. </Text>
<Button title="Click Here To Hide Modal" onPress={() => { this.ShowModalFunction(!this.state.ModalVisibleStatus)} } />
{/* Put All Your Components Here, Which You Want To Show Inside The Modal. */}
</View>
</View>
</Modal>
Now, the thing is whenever I start the Screen by default the Modal remains open. But I have declared the variable ModalVisibleStatus to false initially.
Here's the entire code of My Class-
HelloWorldApp.js
i
mport React, { Component } from 'react';
import {
Text, View, ScrollView, StyleSheet, Image, TextInput, NetInfo, TouchableOpacity,
TouchableHighlight, AsyncStorage, Modal, Alert, Button
} from 'react-native';
import { ICON_NOTE, ICON_TODO, ICON_TAG, ICON_REMINDER, ICON_URGENT, ICON_OFFICE, ICON_PERSONAL, ICON_WORK } from '../actions/ActionTypes';
import LoginScreen from '../components/LoginScreen';
export default class HelloWorldApp extends Component {
state = {
isLoading: false,
getValue: null,
ModalVisibleStatus: false
}
constructor() {
super();
this.state = {
title: '',
details: '',
timestamp: '',
status: '',
url: '',
mail: '',
phone: '',
category: '',
showLoader: false,
showAlert: false,
isNetConnected: true,
catImage: null,
}
};
updateImage(image, category) {
this.setState({
catImage: image,
category: category
})
}
updateValue(text, field) {
if (field == 'title') {
this.setState({
title: text
})
}
else if (field == 'details') {
this.setState({
details: text
})
}
}
ShowModalFunction(visible) {
this.setState({ ModalVisibleStatus: visible });
}
// net connection starts
async componentDidMount() {
const token = await AsyncStorage.getItem('token');
this.setState({ getValue: token });
}
render() {
console.log('#ZZZ2:', this.state.getValue);
return (
<View style={{ flex: 1 }}>
<ScrollView keyboardShouldPersistTaps={'handled'}>
<View style={styles.container}>
<View style={styles.inputContainerEmail}>
<Image style={styles.inputIcon} source={{ uri: this.state.catImage }} />
<TextInput style={styles.inputs}
placeholder="Title"
keyboardType="email-address"
underlineColorAndroid='transparent'
onChangeText={(text) => this.updateValue(text, 'title')} />
</View>
<View style={styles.inputContainerDetails}>
<TextInput style={styles.inputs}
placeholder="Details"
multiline
underlineColorAndroid='transparent'
onChangeText={(text) => this.updateValue(text, 'details')} />
</View>
<ScrollView horizontal={true}>
<View style={{ flexDirection: 'row', flex: 1, marginTop: 10, marginBottom: 10, marginRight: 20, marginLeft: 10 }}>
<TouchableOpacity style={{ justifyContent: 'center', alignItems: 'center', marginRight: 10 }}
onPress={() => { this.updateImage(ICON_NOTE, '1') }}>
<Image style={styles.inputIconCategory} source={{ uri: ICON_NOTE }} />
<Text style={{ marginLeft: 25, marginTop: 5 }}>Note</Text>
</TouchableOpacity>
</View>
</ScrollView>
<TouchableOpacity style={styles.buttonContainerRegister}
onPress={() => {
console.log("#Ctegory:" + this.state.category + "\n Token:" + this.state.getValue + "\nTitle:" + this.state.title + "\nDetails:" + this.state.details + "\Timestamp:" + this.state.timestamp)
}}
>
<Text>Save</Text>
</TouchableOpacity>
<Modal
transparent={false}
animationType={"slide"}
visible={this.state.ModalVisibleStatus}
onRequestClose={() => { this.ShowModalFunction(!this.state.ModalVisibleStatus) }} >
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<View style={styles.ModalInsideView}>
<Text style={styles.TextStyle}>Text Component With Some Sample Text In Modal. </Text>
<Button title="Click Here To Hide Modal" onPress={() => { this.ShowModalFunction(!this.state.ModalVisibleStatus) }} />
{/* Put All Your Components Here, Which You Want To Show Inside The Modal. */}
</View>
</View>
</Modal>
<Button onPress={() => { this.ShowModalFunction(true) }} title="Click Here To Show Modal" />
</View>
</ScrollView>
</View>
);
}
}
So, I want a solution to make the modal closed by default and open it when I click the Button.
That's because it's value is getting undefined. You need to define all states in the constructor.
isLoading:false,
getValue: null,
ModalVisibleStatus: false
cut these var's from state={...}and put them inside the this.state in constructor.
Add ModalVisibleStatus: false into your constructor and cut it from the state
constructor() {
super();
this.state = {
title:'',
details:'',
timestamp : '',
status: '',
url:'',
mail:'',
phone:'',
category:'',
showLoader:false,
showAlert: false,
isNetConnected: true,
catImage: null,
ModalVisibleStatus: false
}
};
put ModalVisibleStatus: false in this.state like this
this.state{
ModalVisibleStatus: false}
I believe that will work.
ShowModalFunction() {
this.setState({
ModalVisibleStatus: !this.state.ModalVisibleStatus
});
}
I am using React Native and React Navigation to build a simple app.
I have got the basic structure working with stub state but I am having problem with changing state via callback and re-render.
In my screen, I have simple start button
`<View style={styles.buttonContainer}>
<TouchableOpacity
style={[myStyles.buttonStyle, { backgroundColor: color }]}
onPress={() => handlePress(button.title)}
>
<Text style={myStyles.textStyle}>{button.title}</Text>
</TouchableOpacity>
</View>`
Problem:
After I update my parent Component state, my child component does not instantly render to match the state change. I understood React will re-render all child components when parent state is changed?
Instead, I have to move back to previous screen and navigate again to my button screen to see that the button's color and text has changed correctly.
I've read about requiring a componentWillReceiveProps(nextProps) handler but I am not sure how to use it. I put a console.log('nextProps', nextProps) inside but it does not get fired.
From navigation perspective, the Root component is on index[0] and my button view is at index[3] so it's the 3rd screen from the root.
EDIT 1: Added Code
myButton screen:
export class TeamsScreen extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.game.name}: Select Team`,
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'black',
},
headerVisible: true
})
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps);
}
render() {
const { navigate, setParams } = this.props.navigation;
const { game, player, setGameState } = this.props.navigation.state.params;
const color = game.status === 'Start' ? 'green' : 'red';
const index = game.indexOf(player);
const status = game.status;
console.log('index', index);
console.log('status', status);
return (
<View style={styles.container}>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[myStyles.buttonStyle, { backgroundColor: color }]}
onPress={() => setGameState(index, status)}
>
<Text style={myStyles.textStyle}>{game.status}</Text>
</TouchableOpacity>
</View>
<View style={styles.buttonContainer}>
<Button
onPress={() => navigate('ChangeDriverScreen', { team, game })}
title='Change Driver'
/>
</View>
<View style={{ marginTop: 40, marginBottom: 20 }}>
<Text style={{ fontSize: 16, color: 'white', alignSelf: 'center' }}>Teams</Text>
</View>
<View style={{ height: 250 }}>
<FlatList
data={player.teams}
renderItem={({item}) =>
<View style={styles.buttonContainer}>
<Button
onPress={() => navigate('TeamSelectedStartScreen', { team: item })}
title={item.name}
/>
</View>}
keyExtractor={item => item.name}
/>
</View>
<Image
style={{ alignSelf: 'center', justifyContent: 'flex-end', height: 75, width: 250, resizeMode: 'stretch'}}
source={require('./images/icons/playerPlaceholder.png')}
/>
</View>
)}}
Then the onPress function that is called back:
setGameState = (gameIndex, status) => {
console.log('setGameState', gameIndex, status);
console.log('gameStateBefore', this.state.game);
const newGameState = this.state.game.map(t => {
console.log(this.state.game.indexOf(t));
if (this.state.game.indexOf(t) === gameIndex) {
const newStatus = status === 'Start' ? 'Stop' : 'Start';
t.status = newStatus; /*eslint no-param-reassign: "off"*/
console.log('inside if', t.status);
console.log('inside if game', t);
return t;
}
return t;
});
console.log('new Game State', newGameState);
this.setState(() => ({
game: newGameState
}));
}
So the setState method works (as re-navigating back to screen 3 shows the correct state but core question is how to get immediate re-render of screen 3 when setState is called from Screen 0.
Hi i'm new in React Native.
I am trying to create two columns layout with space beetween using react native component called flatList.
Here is my view Code:
<View style={styles.container}>
<FlatList
data={db}
keyExtractor={ (item, index) => item.id }
numColumns={2}
renderItem={
({item}) => (
<TouchableWithoutFeedback onPress ={() => showItemDetails(item.id)}>
<View style={styles.listItem}>
<Text style={styles.title}>{item.name}</Text>
<Image
style={styles.image}
source={{uri: item.image}}
/>
<Text style={styles.price}>{item.price} zł</Text>
</View>
</TouchableWithoutFeedback>
)
}
/>
</View>
Here is styles:
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
padding: 10,
marginBottom: 40
},
listItem: {
maxWidth: Dimensions.get('window').width /2,
flex:0.5,
backgroundColor: '#fff',
marginBottom: 10,
borderRadius: 4,
},
And result is two columns but without space between.
Could you help me resolve this problem ?
You can give the item itself a width value of 45%. Also, flatlist has a property called columnWrapperStyle that you can give the value justifyContent: 'space-between.
Heres an example:
<FlatList
columnWrapperStyle={{justifyContent: 'space-between'}}
data={ApiData}
numColumns={2}
renderItem={({item}) => {
return (
<item style={{width: '45%'}} />
);
}}
/>
Use ItemSeparatorComponent for render a compontent between items
Docs: Rendered in between each item, but not at the top or bottom.
<FlatList
data={arrayOfData}
horizontal
ItemSeparatorComponent={
() => <View style={{ width: 16, backgroundColor: 'pink' }}/>
}
renderItem={({ item }) => (
<ItemView product={item} />
)}
/>
Preview in horizontal list
If list is vertical and suppose columnCount is 2
You have to give the styles.container to the contentContainerStyle propety of Flatlist, like so:
<FlatList
data={db}
keyExtractor={ (item, index) => item.id }
contentContainerStyle={styles.container}
numColumns={2}
renderItem={
({item}) => (
<TouchableWithoutFeedback onPress ={() => showItemDetails(item.id)}>
<View style={styles.listItem}>
<Text style={styles.title}>{item.name}</Text>
<Image
style={styles.image}
source={{uri: item.image}}
/>
<Text style={styles.price}>{item.price} zł</Text>
</View>
</TouchableWithoutFeedback>
)
}
/>
Just add some margin to the style of the list Item.
listItem: {
margin: 10,
}
How to create two columns with equal spacing between items:
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
horizontal={false} // you must include this line when using numColumns [per the documentation][1]
numColumns={2} // creates two columns
key={2} // add key to prevent error from being thrown
columnWrapperStyle={{justifyContent: 'space-between'}} // causes items to be equally spaced
>
Also, this will set each column to 1/2 the screen width:
const styles = StyleSheet.create({
item: {
flex: 1/2,
}})
I haven't used this library, but adding padding: 10 to listItem styles should help.
Based on your example it looks like you can add a margin to your list item styles:
listItem: {
maxWidth: Dimensions.get('window').width /2,
flex:0.5,
backgroundColor: '#fff',
marginBottom: 10,
borderRadius: 4,
margin: 18,
},
Keep in mind that this is equivalent to doing:
listItem: {
maxWidth: Dimensions.get('window').width /2,
flex:0.5,
backgroundColor: '#fff',
marginBottom: 10,
borderRadius: 4,
marginTop: 18,
marginBottom: 18,
marginRight: 18,
marginLeft: 18,
},
Customize to your liking or spec :)
Assuming your items are flex:1 and no width specified. You can wrap your renderItem with another view that adds the padding if needed
function columnWrappedRenderItemFunction<ItemT>(
renderItem: ListRenderItem<ItemT>,
numColumns: number,
space: FlatListProps<ItemT>["space"],
numberOfItems: number
): FlatListProps<ItemT>["renderItem"] {
return function columnWrappedRenderItem({
index,
...props
}: Parameters<ListRenderItem<ItemT>>[0]): ReturnType<ListRenderItem<ItemT>> {
const needsGapOnLeft = index % numColumns !== 0;
let extraItems = 0;
if (index === numberOfItems - 1) {
extraItems = (numColumns - (numberOfItems % numColumns)) % numColumns;
}
if (needsGapOnLeft) {
return (
<>
<View style={{width:space}} />
{renderItem({ index, ...props })}
{Array.from({ length: extraItems }, (_, k) => (
<View key={"extra_" + k} style={{marginLeft:space, flex:1}} />
))}
</>
);
} else {
return (
<>
{renderItem({ index, ...props })}
{Array.from({ length: extraItems }, (_, k) => (
<View key={"extra_" + k} marginLeft={space} style={{flex:1}} />
))}
</>
);
}
};
}
e.g.
function myRenderItem() { ... }
...
return (<FlatList
...
data={data}
renderItem={
numColumns === 1
? renderItem
: columnWrappedRenderItemFunction(
renderItem,
numColumns,
space,
data.length
)
}
numColumns={numColumns}
ItemSeparatorComponent={() => <View style={{height: space}} />}
/>);