I'm creating a form, where I need to input the data in the field. But as soon as I press the key, the keyboard is hiding automatically and I'm unable to do persistent typing.
Suppose, I'm typing a Product name or stock anything the typing is persistent but when I type in variants section on every keypress keyboards hides automatically.
Here is the codebase-
AddNewProductScreen.js
....
constructor(props) {
super(props);
this.state = {
product: {
name: '',
category: '',
type: 'packet',
brand: '',
variants: [
{
value: '',
price: '',
},
],
stock: '',
},
};
....
<Input
inputContainerStyle={{borderBottomColor: 'transparent'}}
inputComponent={() => (
<View>
{this.state.product.variants.map((item, index) => (
<View key={index} style={[mainStyles.row, {marginTop: 10}]}>
<View style={mainStyles.col6}>
<Input
label="Value"
placeholder="500 gm or 1 pc"
value={this.state.product.variants[index].value}
onChangeText={value => {
let variants = this.state.product.variants;
variants[index].value = value;
this.setState({
product: {
...this.state.product,
variants,
},
});
}}
/>
</View>
<View style={mainStyles.col5}>
<Input
label="Price"
keyboardType="numeric"
placeholder="50"
value={this.state.product.variants[index].price}
onChangeText={price => {
let variants = this.state.product.variants;
variants[index].price = price;
this.setState({
product: {
...this.state.product,
variants,
},
});
}}
/>
</View>
<View style={[mainStyles.col1, {justifyContent: 'center'}]}>
<Icon
name="times"
size={25}
color="red"
type="font-awesome"
containerStyle={{
display: `${
this.state.product.variants.length > 1 ? 'flex' : 'none'
}`,
}}
onPress={() => {
let variants = this.state.product.variants;
variants.splice(index, 1);
this.setState({
product: {
...this.state.product,
variants,
},
});
}}
/>
</View>
</View>
))}
</View>
)}
/>
....
Expectation: Inside the variants input(Value & Price), I need persistent typing as of other input fields.
Thanks in advance.
P.S- adding the screenshot for reference
{this.state.product.variants.map((item, index) => (
<View key={index} style={[mainStyles.row, {marginTop: 10}]}>
<View style={mainStyles.col6}>
<Input
label="Value"
placeholder="500 gm or 1 pc"
value={this.state.product.variants[index].value}
onChangeText={value => {
let variants = this.state.product.variants;
variants[index].value = value;
this.setState({
product: {
...this.state.product,
variants,
},
});
}}
/>
</View>
Problem here is that this.state.product.variants is changing based on values in the input fields, and thus the contents in this list is mounted again on each keypress. I suggest you try an approach that does not map the input fields and they will remain in the component tree throughout the lifecycle of the parent.
Related
When I submit a form to my api, its weirdly only sending the last input that I filled out on the form. I have all of my form attributes saved to a const object and then sent with an axios post request. This is my first react native app, I don't know what's the cause of this issue. I have another form that doesn't have nested attributes, but saves without issue. How can I send all form inputs together in one api call with my nested form object?
SERVER LOGS
Started POST "/api/v1/applicant_forms" for 127.0.0.1 at 2021-05-02 13:08:14 -0400
Processing by Api::V1::ApplicantFormsController#create as JSON
Parameters: {"applicant_form"=>{"user_id"=>"11", "personal_info_attributes"=>{"prefix"=>"Mr"}}}
ApplicantForm.js
handleSubmit(e) {
e.preventDefault();
const {applicant_form: {personal_info_attributes: {prefix, first_name}}, user_id} = this.state;
const data_obj = {
applicant_form: {
user_id,
personal_info_attributes: {
prefix,
first_name
}
}
}
const { navigation } = this.props;
return axios({
url: `http://127.0.0.1:3000/api/v1/applicant_forms`,
method: 'POST',
data: data_obj,
}).then(response => {
console.log("Successful Response" + response)
navigation.navigate('home')
}).catch(err => {
showMessage({
message: "Something went wrong!",
description: "Testing",
type: 'danger'
})
})
}
render() {
return(
<KeyboardAvoidingView behavior="padding">
<View style={{paddingVertical: 5, paddingHorizontal: 14}}>
<View style={{backgroundColor: '#fff', width: Dimensions.get('window').width * 0.9, minHeight: 50, padding: 2, borderColor: 'gray', borderRadius: 4, borderWidth: 1, height: Dimensions.get('window').height * 0.8}}>
<View style={styles.centerText}>
<Text>Applicant Profile</Text>
</View>
<ScrollView>
<Text>Prefix</Text>
<FormInput
inputStyle={styles.input}
autoCapitalize="none"
onSubmitEditing={() => this.applicant_form.personal_info_attributes.prefix}
autoCorrect={false}
keyboardType='default'
returnKeyType="next"
placeholder='Prefix: Mr. Mrs. Ms., etc'
onChangeText={prefix => this.setState({ applicant_form: {personal_info_attributes: {prefix: prefix} }})}
// value={this.state.applicant_form.personal_info_attributes.prefix}
/>
<Text>First Name</Text>
<FormInput
inputStyle={styles.input}
autoCapitalize="none"
onSubmitEditing={() => this.applicant_form.personal_info_attributes.first_name}
autoCorrect={false}
keyboardType='default'
returnKeyType="next"
placeholder='First Name'
onChangeText={first_name => this.setState({ applicant_form: {personal_info_attributes: {first_name: first_name} }})}
// value={this.state.applicant_form.personal_info_attributes.first_name}
/>
<TouchableOpacity style={styles.saveButtoncontainer} onPress={e=>{this.handleSubmit(e)}}>
<Text style={styles.buttontext}>Save Employment Profile</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
)
You are overwriting the applicant_form value every-time that a text field is edited.
The following code replaces the content in the state applicant_form to be {personal_info_attributes: {prefix: prefix} }. If you had set first_name before, it'll get removed.
<FormInput
...
onChangeText={prefix => this.setState({ applicant_form: {personal_info_attributes: {prefix: prefix} }})}
/>
To prevent this overwriting the object, you can use the spread operator to make sure that the values are cloned and updated accordingly.
<FormInput
onChangeText={prefix =>
this.setState(prevState => ({
applicant_form: {
...prevState.applicant_form,
personal_info_attributes: {
...prevState.applicant_form.personal_info_attributes,
prefix: prefix,
}
}
}))
}
/>
You can read more about this here: What's the best alternative to update nested React state property with setState()?
I have a form that should store data and pass it as a parameter while navigating to a new screen. I have it structured like so. The formik form:
return (
<View style={styles.container}>
<Formik
initialValues={{
first_name: '',
last_name: '',
}}
// Form submission action
onSubmit={(values) => {
const d = addData(values);
this.props.navigation.navigate('Cart', {
screen: 'Process Payment',
params: {data: d},
});
}}>
{(props) => (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : null}
style={{flex: 1, width: '100%'}}>
<ScrollView style={styles.inner}>
<TextInput
style={styles.input}
placeholder="first name"
onChangeText={props.handleChange('first_name')}
value={props.values.first_name}
/>
<TextInput
style={styles.input}
placeholder="last name"
onChangeText={props.handleChange('last_name')}
value={props.values.last_name}
/>
<View style={[{width: '90%', margin: 10, alignSelf: 'center'}]}>
<Button
title="place order"
color="maroon"
onPress={props.handleSubmit}
style={{padding: 3, width: '80%'}}
/>
</View>
</ScrollView>
</KeyboardAvoidingView>
)}
</Formik>
</View>
);
}
Onsubmit function:
onSubmit={(values) => {
const d = addData(values);
this.props.navigation.navigate('Cart', {
screen: 'Process Payment',
params: {data: d},
});
}}>
My Error:
Warning: An unhandled error was caught from submitForm() [TypeError: undefined is not an object (evaluating '_this.props.navigation')]
I'm not sure how to navigate to a new screen within the onsubmit function
onSubmit={(values) => {
const d = addData(values);
props.navigation.navigate('Cart', {
screen: 'Process Payment',
params: {data: d},
});
}}>
I took out the "this" and just called props.navigation.navigate
I already have data in my realtime database. I can also fetch it successfully. Now what I need to do is output it in a tinder-like card view. I am currently outputting data using a 'Demo' file which exports a hard-coded array that contains sample information of some users. Using the demo array, the card output is working. But when I fetch data from firebase and store it in an array named items[], nothing is displayed on my output screen. My code is as follows:
Home Screen
import React from 'react';
import { View, ImageBackground } from 'react-native';
import CardStack, { Card } from 'react-native-card-stack-swiper';
import City from '../components/City';
import Filters from '../components/Filters';
import CardItem from '../components/CardItem';
import styles from '../assets/styles';
import Demo from '../assets/demo';;
import {db} from '../config/config';
class Home extends React.Component {
constructor (props) {
super(props);
this.state = ({
items: []
});
}
componentWillMount() {
var items = [];
db.ref('cards').once('value', (snap) => {
snap.forEach((child) => {
let item = child.val();
item.id = child.key;
items.push({
pet_name: child.val().pet_name,
pet_gender: child.val().pet_gender,
pet_age: child.val().pet_age,
pet_breed: child.val().pet_breed,
photoUrl: child.val().photoUrl,
});
});
//console.log(items)
this.setState({ items: items });
console.log(items);
});
}
render() {
return (
<ImageBackground
source={require('../assets/images/bg.png')}
style={styles.bg}
>
<View style={styles.containerHome}>
<View style={styles.top}>
<City />
<Filters />
</View>
<CardStack
loop={true}
verticalSwipe={false}
renderNoMoreCards={() => null}
ref={swiper => {
this.swiper = swiper
}}
>
{Demo.map((item, index) => (
<Card key={index}>
<CardItem
image={item.image}
name={item.name}
description={item.description}
actions
onPressLeft={() => this.swiper.swipeLeft()}
onPressRight={() => this.swiper.swipeRight()}
/>
</Card>
))}
</CardStack>
</View>
</ImageBackground>
)
}
}
export default Home;
Demo Data
module.exports = [
{
id: 3,
name: 'whatever',
status: 'Online',
match: '78',
description:
'Full-time Traveller. Globe Trotter. Occasional Photographer. Part time Singer/Dancer.',
message:
'I will go back to Gotham and I will fight men Iike this but I will not become an executioner.',
image: require('./images/01.jpg')
},
{
id: 2,
name: 'Clementine Bauch',
match: '93',
description:
'Full-time Traveller. Globe Trotter. Occasional Photographer. Part time Singer/Dancer.',
status: 'Offline',
message: "Someone like you. Someone who'll rattle the cages.",
image: require('./images/02.jpg')
}
];
fetching data:
import {db} from '../config/config';
var items = [];
db.ref('cards').once('value', (snap) => {
snap.forEach((child) => {
let item = child.val();
item.id = child.key;
items.push({
pet_name: child.val().pet_name,
pet_gender: child.val().pet_gender,
pet_age: child.val().pet_age,
pet_breed: child.val().pet_breed,
photoUrl: child.val().photoUrl,
});
});
//console.log(items)
for (var i in items){
console.log(items[i])
}
});
export var items;
I want to use my items[] array instead of the Demo hard-coded array. How do I do this?
The output of items[] array:
Array [
Object {
"pet_age": "11",
"pet_breed": "golden retriever",
"pet_gender": "male",
"pet_name": "dawn",
"photoUrl": "picture",
},
Object {
"pet_age": "7",
"pet_breed": "German",
"pet_gender": "Male",
"pet_name": "Rambo",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/woofmatix-50f11.appspot.com/o/pFkdnwKltNVAhC6IQMeSapN0dOp2?alt=media&token=36087dae-f50d-4f1d-9bf6-572fdaac8481",
},
]
CardItem component:
import React from 'react';
import styles from '../assets/styles';
import { Text, View, Image, Dimensions, TouchableOpacity } from 'react-native';
import Icon from './Icon';
const CardItem = ({
actions,
description,
image,
matches,
name,
pet_name,
pet_gender,
pet_age,
onPressLeft,
onPressRight,
status,
variant
}) => {
// Custom styling
const fullWidth = Dimensions.get('window').width;
const imageStyle = [
{
borderRadius: 8,
width: variant ? fullWidth / 2 - 30 : fullWidth - 80,
height: variant ? 170 : 350,
margin: variant ? 0 : 20
}
];
const nameStyle = [
{
paddingTop: variant ? 10 : 15,
paddingBottom: variant ? 5 : 7,
color: '#363636',
fontSize: variant ? 15 : 30
}
];
return (
<View style={styles.containerCardItem}>
{/* IMAGE */}
<Image source={image} style={imageStyle} />
{/* MATCHES */}
{matches && (
<View style={styles.matchesCardItem}>
<Text style={styles.matchesTextCardItem}>
<Icon name="heart" /> {matches}% Match!
</Text>
</View>
)}
{/* NAME */}
<Text style={nameStyle}>{name}</Text>
{/* DESCRIPTION */}
{description && (
<Text style={styles.descriptionCardItem}>{description}</Text>
)}
{/* STATUS */}
{status && (
<View style={styles.status}>
<View style={status === 'Online' ? styles.online : styles.offline} />
<Text style={styles.statusText}>{pet_age}</Text>
</View>
)}
{/* ACTIONS */}
{actions && (
<View style={styles.actionsCardItem}>
<View style={styles.buttonContainer}>
<TouchableOpacity style={[styles.button, styles.red]} onPress={() => {
this.swiper.swipeLeft();
}}>
<Image source={require('../assets/red.png')} resizeMode={'contain'} style={{ height: 62, width: 62 }} />
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.orange]} onPress={() => {
this.swiper.goBackFromLeft();
}}>
<Image source={require('../assets/back.png')} resizeMode={'contain'} style={{ height: 32, width: 32, borderRadius: 5 }} />
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.green]} onPress={() => {
this.swiper.swipeRight();
}}>
<Image source={require('../assets/green.png')} resizeMode={'contain'} style={{ height: 62, width: 62 }} />
</TouchableOpacity>
</View>
</View>
)}
</View>
);
};
export default CardItem;
Currently working on an app this gives users lists of news on a page and on each news has a textbox where you can input your comment.
So for example, 10 news items will have 10 textboxes.
When a user comment after and hit the submit button, the activity indicator appears for all 10 news items, but I want it to only display on where the comment has been made and also after posting the comment, the comment box should be empty
Function
state = {
posts: [],
comment: ""
};
commentPost = item => {
const api = create({
baseURL: "patch-to-api-url",
headers: { Accept: "application/json" }
});
const self = this;
self.setState({ modalLoader: true });
api
.post("news/posts/" + `${item.id}` + "/comments", {
media: "",
text: this.state.comment
})
.then(response => {
console.log(response);
self.setState({ modalLoader: false });
//updating the state
this.setState(prevState => ({
posts: prevState.posts.map(el => {
if (el.id === item.id) {
return {
...el,
commentsCount: el.commentsCount + 1
};
}
return el;
})
}));
});
};
View
<ScrollView>
{posts.map((item, i) => {
return (
<View key={i} style={styles.user}>
<Card>
<ListItem
titleStyle={{ color: "#36c", fontWeight: "500" }}
onPress={() =>
navigation.navigate("PostComments", {
postID: item.id,
groupID: item.writer.group.id,
communityID: item.group.community.id
})
}
titleNumberOfLines={2}
hideChevron={false}
chevronColor="#36c"
roundAvatar
title={item.headline}
avatar={{
uri:
"https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg"
}}
/>
<Text
style={{
marginBottom: 10,
fontSize: 16,
color: "#000",
fontFamily: "HelveticaNeue-Light"
}}
>
{item.text}
</Text>
<TextInput
onChangeText={onSetComment}
label="Write Comment"
underlineColor="#36a"
style={{ backgroundColor: "#fff", width: "90%" }}
/>
<View>
<Icon
name="md-send"
type="ionicon"
color="#999"
onPress={() => {
onCommentPost(item);
}}
/>
<View style={styles.loading}>
<ActivityIndicator animating={modalLoader} size="small" />
</View>
</View>
</Card>
</View>
);
})}
</ScrollView>
You don't have enough state to accomplish what you want. Wanting an independent spinner in each post implies that you have to store it's state somewhere.
You should add the modalLoader attribute to each post and not globally. Change your function to look like this:
commentPost = item => {
const api = create({
baseURL: "patch-to-api-url",
headers: { Accept: "application/json" }
});
const self = this;
self.setState({ posts: this.state.posts.map(post => post.id === item.id ? {...post, modalLoader: true } : post));
api
.post("news/posts/" + `${item.id}` + "/comments", {
media: "",
text: this.state.comment
})
.then(response => {
console.log(response);
self.setState({ posts: this.state.posts.map(post => post.id === item.id ? {...post, modalLoader: false } : post));
//updating the state
this.setState(prevState => ({
posts: prevState.posts.map(el => {
if (el.id === item.id) {
return {
...el,
commentsCount: el.commentsCount + 1
};
}
return el;
})
}));
});
};
And your component to look like this:
<ScrollView>
{posts.map((item, i) => {
return (
<View key={i} style={styles.user}>
<Card>
<ListItem
titleStyle={{ color: "#36c", fontWeight: "500" }}
onPress={() =>
navigation.navigate("PostComments", {
postID: item.id,
groupID: item.writer.group.id,
communityID: item.group.community.id
})
}
titleNumberOfLines={2}
hideChevron={false}
chevronColor="#36c"
roundAvatar
title={item.headline}
avatar={{
uri:
"https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg"
}}
/>
<Text
style={{
marginBottom: 10,
fontSize: 16,
color: "#000",
fontFamily: "HelveticaNeue-Light"
}}
>
{item.text}
</Text>
<TextInput
onChangeText={onSetComment}
label="Write Comment"
underlineColor="#36a"
style={{ backgroundColor: "#fff", width: "90%" }}
/>
<View>
<Icon
name="md-send"
type="ionicon"
color="#999"
onPress={() => {
onCommentPost(item);
}}
/>
<View style={styles.loading}>
<ActivityIndicator animating={item.modalLoader} size="small" />
</View>
</View>
</Card>
</View>
);
})}
</ScrollView>
You are sharing the modalLoader state among all iterated posts. A Post should be a single stateful component. Then for the specific component that was updated you need to update only that state.
I am new in react-native, i have stuck in one problem.
i have registration page, where there is two section first one is parent registration and second one is student registration.
so parents can add no of child registration form as per there requirement.now if parents wants to remove any child form or block, there is remove information functionality, but when i bind my remove button click event in foreach loop, all the button's parameter value remaining same.so every time when i click on remove button it remove last form.
Here is My code
export default class SignUp extends React.Component {
constructor() {
super()
{
}
this.index = 1;
this.animatedValue = new Animated.Value(0);
this.state = {
username: '', password: '', email: '', phone_number: '', registrationData: [],
valueArray: [], key: 0, index: 0
}
}
AddMore = async () => {
try {
var data = this.state.index + 1;
this.setState({ index: data });
}
catch (error) {
alert(error);
}
try {
this.animatedValue.setValue(0);
let newlyAddedValue = { index: this.index }
this.setState({ disabled: true, valueArray: [...this.state.valueArray, newlyAddedValue] }, () => {
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 500,
useNativeDriver: true
}
).start(() => {
this.index = this.index + 1;
this.setState({ disabled: false });
});
});
}
catch (error) {
alert("Error " + error);
}
}
static navigationOptions = {
title: 'Registration',
}
onRemoveBlock = (key) => {
try {
var r = this.state.valueArray;
var d = r.length - key;
r.splice(d, 1);
this.setState({ valueArray: r });
}
catch (error) {
alert(error)
}
}
render() {
let data = [{
value: 'Male',
}, {
value: 'Female'
}];
this.position = 0;
let newArray = this.state.valueArray.map((item, key) => {
this.position = key + 1;
return (
<View style={styles.container} key={key} >
<ScrollView>
<Button
title={'Remove'}
onPress={() => this.onRemoveBlock(this.position)}
/>
<TextInput
style={styles.input}
placeholder={'First Name'}
autoCapitalize="none"
placeholderTextColor='white'
onChangeText={val => this.onChangeText('childfirstname' + this.position, val)}
/>
</ScrollView>
</View>
);
});
return (
<View style={styles.container}>
<ScrollView>
<Text h1>Parent Registration</Text>
<TextInput
style={styles.input}
placeholder='First Name'
autoCapitalize="none"
placeholderTextColor='white'
onChangeText={val => this.onChangeText('firstname', val)}
/>
<Text h1>Student Registration</Text>
<View style={styles.container} key={123} >
<ScrollView>
<TextInput
style={styles.input}
placeholder={'First Name'}
autoCapitalize="none"
placeholderTextColor='white'
onChangeText={val => this.onChangeText('childfirstname0', val)}
/>
</ScrollView>
</View>
<View style={{ flex: 1, padding: 4 }}>
{
newArray
}
</View>
<Text>{"\n"}</Text>
</ScrollView>
<TouchableOpacity activeOpacity={0.8} style={styles.btn} disabled={this.state.disabled} onPress={this.AddMore}>
<Image source={require('./assets/add.png')} style={styles.btnImage} />
</TouchableOpacity>
</View>
)
}
}
When I click on the remove button, the value of text input was updated with other textfield because of the key. it removes the section which I want to remove so everything works fine, I just need to manage to the index of control properly.
I have updated code and now it is working fine
I just need to assign index value rather than the key to a position in valuearray loop
let newArray = this.state.valueArray.map((item, key) => {
//this.position = key + 1;
this.position=item.index;
return (
<View style={styles.container} key={this.position} >
<ScrollView>
<Button
title={'Actual Key -> '+item.index +' key -> '+key}
onPress={() => this.onRemoveBlock(key)}
key={key}
/>
<TextInput
style={styles.input}
placeholder={'First Name'+this.position}
autoCapitalize="none"
placeholderTextColor='white'
onChangeText={val => this.onChangeText('childfirstname' + this.position, val)}
/>
</ScrollView>
</View>
);
});
so now control value will remain the same always