react hook form multiple control on one Controller - react-native

I have a text input component that uses text input from react native paper, I want to make a place autocomplete by calling google place autocomplete API
right now I can display the suggestion but I can't change the text input value with the value of the suggestion that has been clicked
screenshot of component
since I use Controller from react hook form I thought I could use setValue from useForm to change the value but it didn't do anything when I try to call setValue to change textInput value to one of the suggested value
import React from "react";
import { FlatList, StyleSheet, TouchableOpacity, View } from "react-native";
import { Text, TextInput, Colors } from "react-native-paper";
import { Controller, useForm } from "react-hook-form";
import axiosInstance from "services/axiosInstance";
export default React.forwardRef(
(
{
name,
label,
placeholder,
control,
style: addOnStyle,
...props
},
ref
) => {
const { setValue } = useForm();
const [addressList, setAddressList] = React.useState([])
const getAddressList = async (input) => {
if (input == null || input.match(/^ *$/) !== null) {
setAddressList([])
} else {
const response = await axiosInstance.get(
`https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${input}&components=country:us&language=en&key=API_KEY`
)
setAddressList([])
if (response?.data?.status === "OK") {
response?.data?.predictions?.map((item) => setAddressList(addressList => [...addressList, item.description]))
} else {
setAddressList(["Address not found."])
}
}
}
return (
<View style={{ ...styles.viewInput, ...addOnStyle }}>
<Controller
control={control}
name={name}
defaultValue=""
render={({
field: { onChange, onBlur, value, name },
fieldState: { error },
}) => {
return (
<>
<TextInput
label={label}
name={name}
placeholder={placeholder}
onBlur={onBlur}
onChangeText={(val) => onChange(val, getAddressList(val))}
error={!!error?.message}
value={value}
ref={ref}
{...props}
/>
{error?.message ? (
<Text style={styles.textError}>{error?.message}</Text>
) : null}
{addressList.length > 0 ?
<View style={styles.addressListContainer}>
<FlatList
keyExtractor={(_, i) => String(i)}
data={addressList}
renderItem={({ item, index }) => {
return (
<TouchableOpacity
activeOpacity={1}
style={[styles.addressListItem, index==0 ? {borderTopWidth: 0} : {borderTopWidth: 1}]}
onPress={() => {setAddressList([]), setValue(name, item)}}
>
<Text numberOfLines={1}>{item}</Text>
</TouchableOpacity>
)
}}
/>
</View>
: null}
</>
);
}}
/>
</View>
);
}
);
UPDATE Changed the title to match the current question
I think for now my problem is since the control is set from the outside of the component that makes it can't be changed with setValue from inside the component, now I wonder if we could use multiple control on one Controller?

I solve it by changing setValue(name, item) on onPress to onChange(item) it doesn't need another control

Related

Use .map not flatlist with activityIndicator in react native

I am getting a list of data using map not flatlist, I want to use .map not flatlist, when I apply ActivityIndicator to it while fetching the data, it did not work see below
Below is my code
<View>
{dataList.map((dataList, index) =>
<Text key={index} >{dataList.category_name}</Text>
<Text key={index} >{dataList.category_number}</Text>
)}
</View>
When I tried it with ActivityIndicator see below
{ dataList ?
<View>
{dataList.map((dataList, index) =>
<Text key={index} >{dataList.category_name}</Text>
<Text key={index} >{dataList.category_number}</Text>
)}
</View>
:
<ActivityIndicator />
}
It the not work, I will need your help with this.
Thanks in advance
Try using a boolean for your conditional render such as a loading state which you can easily toggle on and off at the beginning of the fetch and at the end respectively. You can also state the logic of your map outside of your component so it looks way cleaner and easy to read like this example:
import React from 'react';
import { Text, View, ActivityIndicator } from 'react-native';
const dataList = [
{ category_name: 'pop', category_number: 1 },
{ category_name: 'dance', category_number: 2 },
{ category_name: 'hiphop', category_number: 3 },
];
const renderDataList = () => {
return dataList.map((dataList, index) => (
<View>
<Text>{dataList.category_name}</Text>
<Text>{dataList.category_number}</Text>
</View>
));
};
const App = () => {
const [isLoading, setIsloading] = React.useState(false);
return <View>{isLoading ? <ActivityIndicator /> : renderDataList()}</View>;
};
export default App;
Your output would be for false:
Your output for true would be:

React Native dynamically filled Value of InputText returns empty on form Submit

I'm new to React Native mobile app, I have a profile page with few Input Text fields. I am setting the field values to the current value in the DataBase. All the fields populate with the latest values as required.
The problem is on submitting the form the data returns empty values of the form fields which are dynamically populated. If the form Field is empty and the user manually enters a value. That value is returns on submission.
The Code is below (Updated with a shorter code )
import React,{useState} from 'react'
import { StyleSheet, Text, View,TextInput,Button, SafeAreaView } from 'react-native'
import { Formik } from "formik";
export default function App() {
const [theEmail, setTheEmail] = useState()
const [theFlag, setTheFlag] = useState(true)
if(theFlag)
{
setTheFlag(false)
setTheEmail("test#test.com")
}
return (
<SafeAreaView>
<Formik
initialValues={{ email: '' }}
onSubmit={values => console.log(values)}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<TextInput style={styles.textInput}
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={theEmail}
onChangeText = {(text)=>setTheEmail(text)}
/>
<Button onPress={handleSubmit} title="Submit" />
</View>
)}
</Formik>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
textInput : {
borderColor : "red",
borderWidth : 1,
margin : 20,
height : 50,
}
})
Appreciate the help to solve this issue.
I figured out the solutions to the problem. Hope it helps someone else with this issue.
Instead of passing the value from database to the value property of the TextInput and setting the onChangeText to the state variable, I passed the state variable to initialValues of <Formik> ( and in my original file which got the values from the database through an async function, I conditionally rendered the form to avoid the state variable passing the value to initialValues before the state is set.)
The resolved code
import React,{useState} from 'react'
import { StyleSheet, Text, View,TextInput,Button, SafeAreaView } from 'react-native'
import { Formik } from "formik";
export default function App() {
const [theEmail, setTheEmail] = useState()
const [theFlag, setTheFlag] = useState(true)
const [theRender, setTheRender] = useState(false)
if(theFlag) // << ==== if(response.ok && theFlag) // in original file check for response.ok to get data from database
{
setTheFlag(false)
setTheEmail("test#test.com")
setTheRender(true) //<< set the state value to true to confirm value passed from async
}
return (
<SafeAreaView>
{theRender && // conditionally rendering the form
<Formik
initialValues={{ email: theEmail }} // theEmail value is passed
onSubmit={values => console.log(values)}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<TextInput style={styles.textInput}
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
/>
<Button onPress={handleSubmit} title="Submit" />
</View>
)}
</Formik>
}
</SafeAreaView>
)
}
const styles = StyleSheet.create({
textInput : {
borderColor : "red",
borderWidth : 1,
margin : 20,
height : 50,
}
})
Now the field is prepopulated with the data from the database and is editable and it submits the default value prefilled or the updated text content.

React Native: how to handle state for each item in a rendered FlatList when pressing the 'like' button?

I'm trying to handle the state for a 'heart' icon in a rendered Flat List (which loads data from Firebase) for each individual item within the Flat List.
The code works, in that the heart icon fills in and the data is pushed to the database when the icon is pressed. Likewise, pressing the heart icon again reverts the icon and removes the 'like' from the database.
However, when the heart icon is clicked, it swaps between the filled in state and hollow state for the heart icon for every item in the list, when I'm trying to alter state for that specific item.
I understand that I need to handle state locally for each item in the Flat List, but I've no idea how to do it. Any help would be appreciated. Code below:
import React, {Component} from 'react';
import {
FlatList,
Text,
View,
} from 'react-native';
import {Icon} from 'react-native-elements';
import {globalStyles} from '../config/Styles';
import Firebase from 'firebase';
import 'firebase/database';
export default class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
//set value of postList variable as an empty array
postList: [],
liked: false,
};
}
componentDidMount() {
this.getPostData();
}
getPostData = () => {
const ref = Firebase.database().ref('/posts');
ref.on('value', snapshot => {
const postsObject = snapshot.val();
if (!postsObject) {
console.log('NO DATA IN FIREBASE:', Date(Date.now()));
} else {
console.log('HOMESCREEN FIREBASE DATA RETRIEVED:', Date(Date.now()));
const postsArray = Object.values(postsObject);
this.setState({postList: postsArray});
}
});
};
render() {
return (
<View>
<FlatList
keyExtractor={post => post.id}
data={this.state.postList}
renderItem={({item: post}) => (
<View style={globalStyles.postContainer}>
<Text style={globalStyles.postText}>
{post.heading}
{'\n'}#{' '}
<Text style={{fontWeight: 'bold'}}>{post.location}</Text>
{'\n'}
{post.description}
{'\n'}
listed by{' '}
<Text style={{fontWeight: 'bold'}}>{post.createdBy}</Text>
{'\n'}
on <Text style={{fontWeight: 'bold'}}>{post.createdAt}</Text>
</Text>
<View style={globalStyles.iconMargin}>
<Icon
raised
iconStyle={globalStyles.icon}
name={this.state.liked ? 'heart' : 'heart-o'}
size={28}
type="font-awesome"
onPress={() => {
const userKey = Firebase.auth().currentUser.uid;
const postKey = post.id;
const favRef = Firebase.database().ref(
'favourites/' + userKey + '/' + postKey,
);
if (this.state.liked === false) {
favRef.set({
id: postKey,
heading: post.heading,
description: post.description,
location: post.location,
createdAt: post.createdAt,
createdBy: post.createdBy,
});
this.setState({liked: true});
} else {
favRef.remove();
this.setState({liked: false});
}
}}
/>
<Icon
raised
iconStyle={globalStyles.icon}
name="flag-o"
size={28}
type="font-awesome"
onPress={() =>
this.props.navigation.navigate('ReportPostScreen', post)
}
/>
</View>
</View>
)}
/>
</View>
);
}
}
Ok so the issue is that you've got a singular liked state value instead of an array. You should firstly change liked to an array (which will store the id of the posts which are liked). Maybe call it something more appropriate such as likePosts. Then you can add or remove post ids from the array when they're liked or unliked (and check the likedPosts array for the value when deciding what icon to display).
Your modified code should look something like this:
import React, {Component} from 'react';
import {
FlatList,
Text,
View,
} from 'react-native';
import {Icon} from 'react-native-elements';
import {globalStyles} from '../config/Styles';
import Firebase from 'firebase';
import 'firebase/database';
export default class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
//set value of postList variable as an empty array
postList: [],
likedPosts: [],
};
}
componentDidMount() {
this.getPostData();
}
getPostData = () => {
const ref = Firebase.database().ref('/posts');
ref.on('value', snapshot => {
const postsObject = snapshot.val();
if (!postsObject) {
console.log('NO DATA IN FIREBASE:', Date(Date.now()));
} else {
console.log('HOMESCREEN FIREBASE DATA RETRIEVED:', Date(Date.now()));
const postsArray = Object.values(postsObject);
this.setState({postList: postsArray});
}
});
};
render() {
return (
<View>
<FlatList
keyExtractor={post => post.id}
data={this.state.postList}
renderItem={({item: post}) => (
<View style={globalStyles.postContainer}>
<Text style={globalStyles.postText}>
{post.heading}
{'\n'}#{' '}
<Text style={{fontWeight: 'bold'}}>{post.location}</Text>
{'\n'}
{post.description}
{'\n'}
listed by{' '}
<Text style={{fontWeight: 'bold'}}>{post.createdBy}</Text>
{'\n'}
on <Text style={{fontWeight: 'bold'}}>{post.createdAt}</Text>
</Text>
<View style={globalStyles.iconMargin}>
<Icon
raised
iconStyle={globalStyles.icon}
name={this.state.likedPosts.indexOf(post.id) > -1 ? 'heart' : 'heart-o'}
size={28}
type="font-awesome"
onPress={() => {
const userKey = Firebase.auth().currentUser.uid;
const postKey = post.id;
const favRef = Firebase.database().ref(
'favourites/' + userKey + '/' + postKey,
);
// This checks that the array doesn't contain the post id (i.e. the post was not previously liked)
if (this.state.likedPosts.indexOf(post.id) === -1) {
favRef.set({
id: postKey,
heading: post.heading,
description: post.description,
location: post.location,
createdAt: post.createdAt,
createdBy: post.createdBy,
});
// Include the post.id in the likedPosts array
this.setState({ likedPosts: [...this.state.likedPosts, post.id] })
} else {
favRef.remove();
// Remove the post.id from the likedPosts array
let index = this.state.likedPosts.indexOf(post.id);
this.setState({ likedPosts: this.state.likedPosts.splice(index, 1) })
}
}}
/>
<Icon
raised
iconStyle={globalStyles.icon}
name="flag-o"
size={28}
type="font-awesome"
onPress={() =>
this.props.navigation.navigate('ReportPostScreen', post)
}
/>
</View>
</View>
)}
/>
</View>
);
}
}
becuase this.state.liked will be true for all items in the json respone
to correct it you can update the state array json
ItemPRessed =(index)=>{let dataArray = this.state.data
dataArray[index].liked = !dataArray[index].liked
this.setState({
data:dataArray
})}
and instead of this.state.liked use post.liked so it will be specific to the item
and instead of this.setState({liked: true});
put
this.ItemPRessed(Index)
i don't know how your indexs work in your json put if it is like this
[{item},{item}]
then you can use renderItem=({item: post, index}) instead of renderItem={({item: post})
to get the index on which item it is pressed then

Use modal to take user checkbox input and return data back to parent component

I have a Newevent class component which takes several user inputs. One of them is an array of user ids called access list and the rest is text input. Here is the code I am thinking about:
export default class NewEvent extends React.Component {
state = {
name: '',
access_list: [],
modalVisible: false
};
triggerModal() {
this.setState(prevState => {
return {
display: true
}
});
}
render() {
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder='Name'
autoCapitalize="none"
placeholderTextColor='white'
onChangeText={val => this.onChangeText('name', val)}
/>
<Button
onPress = { () => this.triggerModal() }
title = "Open Modal"
color = "orange">
</Button>
<DisplayModal
visible={this.state.modalVisible}
/>
<Button
title='Save'
onPress={this.save}
/>
</View>
)
}
}
In DisplayModal.js, it is a function component to display a series of checkbox allowing user to check which user he wants to include in access list:
import React from 'react'
import { Modal, View, Image, Text, StyleSheet } from 'react-native';
import { CheckBox } from 'react-native-elements'
const DisplayModal = (props) => (
<Modal visible={ props.display } animationType = "slide"
onRequestClose={ () => console.log('closed') }>>
<View>
<CheckBox
title='Click Here1'
checked={this.state.checked}
/>
<CheckBox
title='Click Here2'
checked={this.state.checked}
/>
</View>
</Modal>
)
Any recommendation about constructing DisplayModal.js to return the user's selection?
I'm not sure about the problem but I think that you want to send states from DisplayModal to Newsevent for that purpose at least there are two methods:
1- Using redux which is a little(or too) complicated. you can read the documentation.
2- use a props which is a function that returns data from the child component to the parent.
for example:
export class DisplayModal extends Component {
state = {
item1: false,
item2: false
};
_updateState = item => {
this.setState({ item: !this.state[item] });
// use the getState props to get child state
return this.props.getState(this.state);
};
render() {
return (
<Modal
visible={props.display}
animationType="slide"
onRequestClose={() => console.log("closed")}
>
>
<View>
<CheckBox
title="Click Here1"
checked={this.state.checked}
onPress={() => this._updateState("item1")}
/>
<CheckBox
title="Click Here2"
checked={this.state.checked}
onPress={() => this._updateState("item2")}
/>
</View>
</Modal>
);
}
}
and use the getState props to get user's selections:
<DisplayModal getState={(items)=> this.setState({userSelections:items}) />
after each action on DisplayModal i.e on checkbox press, _updateState call the function the function in props and in your example, the function gets data from the child and update the parent state.

How to dynamically add a text input in React Native

How can I add a text input in React Native with the click of a button? For example, I would press the "+" button and it would add a text input at the bottom of the View.
EDITED:
Here is my code (deleted all the irrelevant stuff). Not working for some reason. Clicking the button doesn't do anything.
import React, { Component, PropTypes } from 'react';
import { StyleSheet,NavigatorIOS, Text, TextInput, View, Button,
TouchableHighlight, TouchableOpacity, ScrollView, findNodeHandle,
DatePickerIOS} from 'react-native';
import TextInputState from 'react-native/lib/TextInputState'
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {textInput: [],
date: new Date(),
}
}
addTextInput = (key) => {
let textInput = this.state.textInput;
textInput.push(<TextInput key={key} />);
this.setState({ textInput })
}
render(){
return(
<View>
<Button title='+' onPress={() =>
this.addTextInput(this.state.textInput.length)} />
{this.state.textInput.map((value, index) => {
return value
})}
</View>
)
}
}
this is an example for that :
import React, { Component } from 'react';
import { AppRegistry, View, Text, Button, TextInput} from 'react-native';
class App extends Component {
constructor(props){
super(props);
this.state = {
textInput : []
}
}
addTextInput = (key) => {
let textInput = this.state.textInput;
textInput.push(<TextInput key={key} />);
this.setState({ textInput })
}
render(){
return(
<View>
<Button title='+' onPress={() => this.addTextInput(this.state.textInput.length)} />
{this.state.textInput.map((value, index) => {
return value
})}
</View>
)
}
}
maybe that can help you :)
I have a solution that begins with a single text input. It has an "add" button that adds another text input just below the first. That new input keeps the "add" button, and all previous inputs above change to a "remove" button, with which, of course, the user can remove the corresponding view. I could only get it to work by handling state in a React Redux store, and so the code is spread out between too many different files to post here, but anyone interested can view it on GitHub or Snack.
I know this is an old post, but this is a problem I wish was answered when I first came here.
Here is example of dynamic add remove input
let obj = { text: '' }
this.state = {
attributeForm: [{ [1]: obj }],
duplicateAttributes: [1]
}
addAtributeRow() {
const { duplicateAttributes, attributeForm } = this.state;
let pushNumber = 1;
if (duplicateAttributes.length > 0) {
let max = Math.max(...duplicateAttributes);
pushNumber = max + 1
}
let arr = duplicateAttributes;
arr.push(pushNumber)
let obj = { text: '' }
this.setState({
attributeForm: [...attributeForm, { [pushNumber]: obj }]
})
this.setState({
duplicateAttributes: arr
})
}
deleteAttributeRow(number) {
const { duplicateAttributes, attributeForm } = this.state;
const index = duplicateAttributes.indexOf(number);
if (index > -1) {
duplicateAttributes.splice(index, 1);
let findedIndex;
for (let i = 0; i < attributeForm.length; i++) {
// var index = Object.keys(attributeForm[i]).indexOf(index);
if (Object.keys(attributeForm[i])[0] == number) {
findedIndex = i;
}
}
if (findedIndex > -1) {
attributeForm.splice(findedIndex, 1);
}
}
this.setState({
attributeForm: attributeForm,
duplicateAttributes: duplicateAttributes
})
}
render() {
const {attributeForm} = this.state;
{
duplicateAttributes.length > 0 && duplicateAttributes.map((item, index) =>
<View >
<Item style={GStyle.borderStyle} >
<Textarea placeholder="Text"
style={[GStyle.placeholder.text, { width: wp('90%') }]}
keyboardType="default"
autoCorrect={true}
autoCapitalize={'words'}
rowSpan={4}
value={attributeForm[index][item]['text']}
placeholderTextColor={GStyle.placeholder.color}
onChangeText={(text) => this.addAttributes(item, text, 'text')}
returnKeyLabel='done'
/>
</Item>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginHorizontal: wp('30%') }}>
{
<Button full rounded onPress={() => { this.deleteAttributeRow(item) }} >
<Icon name="minus" type="FontAwesome5" style={{ fontSize: wp('4%') }} />
</Button>
}
</View>
</View>
}
<Button full rounded onPress={() => { this.addAtributeRow() }} >
<Icon name="plus" type="FontAwesome5" style={{ fontSize: wp('4%') }} />
</Button>
}
If you want to do this with Hooks or Functional component then here is
the link of Expo
https://snack.expo.dev/#muhammadabdullahrishi/add-input
I have included how to add and delete Text Input
with hooks