React-Native: Setting focus to custom component built off an array - react-native

I am trying to create a list of custom inputs based on an array, and when pressing the the enter key, I'd like the focus to automatically move to the next custom input. I can get this to work with a regular <TextInput> react component using the ref and onSubmitEditing but I cannot get this to function properly using my custom component that wraps a <TextInput>
Here is my code, it consists of two files: App.js and TextInput2.js (I know that currently the last line will error because of the reference counter but if I can get it to work I'll address the last issue)
Working Snack
-- App.js --
import React from 'react';
import { StyleSheet, View, TextInput } from 'react-native';
import TextInput2 from './TextInput2'
export default class App extends React.Component {
constructor(){
super();
this.myRef = [];
this.state = {}
}
focusField = (key) => {
this.myRef[key].focus()
}
render() {
let textFields = ["one", "two", "three", "four", "five"];
return (
<View style={styles.container}>
{
textFields.map((x, i) => {
this.myRef[i] = React.createRef();
let k = i + 1
return(
<TextInput2
name={x}
key={i}
placeholder={x + " This Doesnt Work"}
ref={ref => this.myRef[i] = ref}
nextRef={this.myRef[k]}
//onSubmitEditing={() => this.focusField(k)}
//onSubmitEditing={() => this.myRef[k].focus()}
blurOnSubmit={false}
/>
)
})
}
{
textFields.map((x, i) => {
this.myRef[i] = React.createRef();
return(
<TextInput
name={x}
key={i}
placeholder="This works!"
ref={ref => this.myRef[i] = ref}
onSubmitEditing={() => this.focusField(i+1)}
blurOnSubmit={false}
/>
)
})
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
--TextInput2.js --
import React from 'react';
import { View, TextInput } from 'react-native';
export default class TextInput2 extends React.Component {
state={}
handleFocus = () => {}
handleBlur = () => {}
focus() {
this.props.nextRef.focus()
}
render() {
return (
<View >
<TextInput
{...this.props}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onSubmitEditing={() => this.focus()}
/>
</View>
)
}
}
I've read this post and this but cannot seem to determine how to setup the function to set focus on the next field.

I have edited the Snack. Please try this
I think you're making it complicated. Try to change like this,
this.myRef[index] = React.createRef()
CustomTextComponent component
<CustomTextComponent
name={Something}
key={index}
forwardRef={this.myRef[index]}
onSubmitEditing={() => this.myRef[index + 1].current.focus()}
/>
As you're using createRef() you have to call it's ref using the "current" object.
CustomComponent.js
import React from 'react';
import { View, TextInput } from 'react-native';
export default class CustomComponent extends React.Component {
render() {
return (
<View >
<TextInput
{...this.props}
returnKeyType={"next"}
ref={this.props.forwardRef}
onSubmitEditing={this.props.onSubmitEditing}
/>
</View>
)
}
}

Related

How to use createRef to toggle Accordion list items

I have a FlatList and each item is an accordion, I m using class based react and I want to be able to toggle each accordion individually using createRef but I was unsuccessful
export default class shopingScreen extends React.component{
constractor(props){
super(props);
this.state = {
showAccordion : false
}
this.accordian = React.createRef();
}
handleListItem(item,index){
return (
<TouchableOpacity ref={this.accordian} onPress={()=> {this.setState(prevState =>({!prevState.showAccordion}) )
<Icon name='chevron-up'/>
</TouchableOpacity>
{this.state.showAccordion&&<Text>{item}</Text>
}
renderList(){
return (
<View>
<FlatList
data ={fakeList}
keyExtractor ={(item,index)=> Math.random().toString()}
renderItem={({item,index})=> this.handleListItem(item,index)}
</View>
)
}
}
Every thing gets much easier if you take handleListItem and make it its own component. Each item needs its own accordion, its own boolean state, its own ref, and its own Animation.Value (for the accordion effect). If you tried to manage all that logic in a single component it gets really messy (see AssetExample.js here)
But when separated your list component from the list item component everything is much cleaner link
// List component
import React from 'react';
import { View, FlatList, StyleSheet } from 'react-native';
import { colorGenerator } from '#phantom-factotum/colorutils';
import ListItem from './ListItem';
const fakeList = colorGenerator(5).map((color, i) => ({
color,
title: 'Item ' + (i + 1),
id: 'list-item-' + i,
}));
export default class ShoppingScreen extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<View>
<FlatList
data={fakeList}
keyExtractor={(item, index) => item.id}
renderItem={({ item, index }) => (
<ListItem item={item} index={index} />
)}
/>
</View>
);
}
}
const styles = StyleSheet.create({});
// list item
import React from 'react';
import { MaterialCommunityIcons } from '#expo/vector-icons';
import {
View,
FlatList,
TouchableOpacity,
Text,
Animated,
StyleSheet,
} from 'react-native';
const ITEM_HEIGHT = 50;
export default class ListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
showAccordion: false,
};
this.itemHeight = new Animated.Value(0);
this.itemRef = React.createRef(null);
}
render() {
const showAccordion = this.state.showAccordion;
const animatedStyle = {
height: this.itemHeight.interpolate({
inputRange: [0, 1],
outputRange: [0, ITEM_HEIGHT],
}),
overflow: 'hidden',
};
return (
<TouchableOpacity
style={[
styles.itemContainer,
{ backgroundColor: this.props.item.color },
]}
ref={this.itemRef}
onPress={() => {
const nextVal = !showAccordion;
Animated.timing(this.itemHeight, {
toValue: nextVal ? 1 : 0,
duration: 200,
}).start();
this.setState((prevState) => ({
...prevState,
showAccordion: nextVal,
}));
}}>
<MaterialCommunityIcons
name={showAccordion ? 'chevron-up' : 'chevron-down'}
/>
<Animated.View style={animatedStyle}>
<Text>{this.props.item.title}</Text>
</Animated.View>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
itemContainer: {
padding: 5,
paddingVertical: 10,
marginVertical: 10,
// overflow: 'hidden',
},
});

How to call a component onPress TouchableOpacity in react-native

I am using TouchableOpacity from react-native. The code is:
<TouchableOpacity onPress={onPress}>
<Text style={styles.supplementItem}>{item.item}</Text>
</TouchableOpacity>
where the OnPress function is as:
const onPress = () => (
// eslint-disable-next-line no-sequences
<Text style={styles.supplementItem}>Hello</Text>
this.setState({tempKey: tempKey + 1})
);
I looked at: this question and tried to do like it. But this is not working. I am setting my states as follows:
constructor(props) {
super(props);
this.state = {
tempKey: 0,
};
}
Kindly help me what I am doing wrong.
The question which you mentioned is using function based components and you are using class based components as you showed the costructor part which proves that.
So onPress must be a method in that class in your case, so you don't need the const keyword before it and you need to call it in this way ; this.onPress
In nutshell, your whole component should be like this;
import React from 'react';
import {Text, TouchableOpacity, View} from 'react-native';
class YourComp extends React.Component {
constructor(props) {
super(props);
this.state = {
tempKey: 0,
show: false
};
}
onPress = () => {
// eslint-disable-next-line no-sequences
this.setState(prevState => ({tempKey: prevState.tempKey + 1}))
};
render() {
return (
<View>
<TouchableOpacity style={{height: 100, justifyContent: 'center', alignItems: 'center'}} onPress={() => this.onPress()}>
<Text>Hello (item.item in your case)</Text>
</TouchableOpacity>
<Text key={this.state.tempKey.toString()}>Hello {this.state.tempKey}</Text>
</View>
)
}
}
export default YourComp;
If you want to show some component conditionally;
this.state = {
...,
show: false
}
Then in the onPress method;
this.setState(prevState => ({show: !prevState.show})) // this will make <Text /> to toggle the modal when clicked.
Then in the render method;
<Text>
{this.state.show && (
<YourNewComponent />
) || null}
</Text>

How to Use method of functional component into class component in react native?

I am working in one react native project in which, I want to make common component for show loading indicator (for inform user to wait until process done.)
For that , I have make one js file that is common for my project
Look like below :
Loader.JS : Common functional component in react native
import React, {useState} from 'react';
import {View, StyleSheet, ActivityIndicator} from 'react-native';
import {loaderColor} from './app.constants';
const Loader = () => {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={loaderColor} />
</View>
);
};
const UseLoader = () => {
const [visible, setVisible] = useState(true);
const showLoader = () => setVisible(true);
const hideLoader = () => setVisible(false);
const loader = visible ? <Loader /> : null;
return [loader, showLoader, hideLoader];
};
const styles = StyleSheet.create({
loadingContainer: {
backgroundColor: 'red',
flex: 1,
position: 'absolute',
...StyleSheet.absoluteFillObject,
alignItems: 'center',
justifyContent: 'center',
zIndex: 100,
padding: 10,
},
});
export default UseLoader;
And my class component is look like this :
import React, {Component} from 'react';
import {View} from 'react-native';
// import {UseLoader} from '../UseLoader';
import '../UseLoader';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
[loader, showLoader , hideLoader] = UseLoader;
this.callApi()
}
callApi() {
...
}
render() {
return (
<View style={styles.body}>
{loader}
</View>
);
}
}
I have tried to import functional component in both way But failed to use it.
Is any solution that can Import functional component in class component in react native ?
you can use this.
You can add a ref to the child component:
<loader ref='loader' {...this.props} />
Then call the method on the child like this:
<Button onPress={this.refs.loader.myfunc} />
Same functionality, but instead of using a String to reference the component, we store it in a global variable instead.
<loader ref={loader => {this.loader = loader}} {...this.props} />
<Button onPress={this.loader.myfunc} />
If you want to do it common, change the state on the class component, where you send if it is visible or not, like this:
const Loader = (props) => {
if(props.show){
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={loaderColor} />
</View>
);
}else{
return null;
}
};
and in your class component
import React, {Component} from 'react';
import {View} from 'react-native';
// import {UseLoader} from '../UseLoader';
import '../UseLoader';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.setState({showLoading:true});
this.callApi()
}
callApi() {
...
}
render() {
return (
<View style={styles.body}>
<loader show={this.state.showLoading} />
</View>
);
}
}

How to prevent initial load from Algolia instant search in react native?

I have a users collection which I want to query as the user types, but when I switch to the Search Users Screen, all the users have populated on the screen even the search is empty.
How can I prevent this and only send the results to the client when he/she types in the search box??
I found some other links and resources but either they were too old, difficult to understand for me or using React instead of React Native.
I am a beginner developer and if you would be elaborate with the solution then that would be great.
This is my code:
SEARCH_USER.JS:
import React, {Component} from 'react';
import {
View,
Text,
TextInput,
Alert,
FlatList,
ActivityIndicator,
} from 'react-native';
import firestore from '#react-native-firebase/firestore';
import algoliasearch from 'algoliasearch';
import {
InstantSearch,
connectSearchBox,
connectInfiniteHits,
} from 'react-instantsearch-native';
import PropTypes from 'prop-types';
import InfiniteHits from '../components/Algolia Components/InfiniteHits';
class SearchAddBuddyScreen extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
value: null,
};
}
searchClient = algoliasearch(
'########',
'####################',
);
render() {
return (
<View>
<SecondaryHeader secondaryHeaderTitle="Add Buddies" />
<InstantSearch
searchClient={this.searchClient}
indexName="prod_USERSCOLLECTION"
onSearchStateChange={searchState =>
console.log('=====> ', searchState)
}>
<ConnectedSearchBox />
<InfiniteHits navigation={this.props.navigation} />
</InstantSearch>
</View>
);
}
}
class SearchBox extends Component {
render() {
console.log('Connected Search Box called');
return (
<View
style={[
textInput.generalTextInput,
{
marginBottom: 24,
alignSelf: 'center',
justifyContent: 'center',
},
]}>
<TextInput
placeholderTextColor="#333647"
style={[textInput.generalTextInput, {alignSelf: 'center'}]}
onChangeText={text => this.props.refine(text)}
value={this.props.currentRefinement}
placeholder={'Search'}
clearButtonMode={'always'}
spellCheck={false}
autoCorrect={false}
autoCapitalize={'none'}
/>
</View>
);
}
}
SearchBox.propTypes = {
refine: PropTypes.func.isRequired,
currentRefinement: PropTypes.string,
};
const ConnectedSearchBox = connectSearchBox(SearchBox);
export default SearchUserScreen;
InfiniteHits.js:
import React, {Component} from 'react';
import {StyleSheet, Text, View, FlatList} from 'react-native';
import {connectInfiniteHits} from 'react-instantsearch-native';
import AddFriendComponent from '../AddFriend';
class InfiniteHits extends Component {
constructor(props) {
super(props);
this.navigation = this.props.navigation;
}
_renderItem = ({item}) => {
return (
<AddFriendComponent
username={item.username}
fullName={item.fullName}
onPressUserProfile={() =>
this.props.navigation.navigate('UserProfile', {profileId: item.uid})
}
/>
);
};
render() {
return (
<FlatList
data={this.props.hits}
keyExtractor={item => item.objectID}
// ItemSeparatorComponent={() => <View style={styles.separator} />}
onEndReached={() => this.props.hasMore && this.props.refine()}
renderItem={this._renderItem}
/>
);
}
}
export default connectInfiniteHits(InfiniteHits);
After reading through more material I found this https://www.algolia.com/doc/guides/building-search-ui/going-further/conditional-requests/react/
And made the following changes to my code and it succeeded.
conditionalQuery = {
search(requests) {
if (
requests.every(({params}) => !params.query) ||
requests.every(({params}) => params.query === ' ') ||
requests.every(({params}) => params.query === ' ') ||
requests.every(({params}) => params.query === ' ')
) {
// Here we have to do something else
console.log('Empty Query');
return Promise.resolve({
results: requests.map(() => ({
hits: [],
nbHits: 0,
nbPages: 0,
processingTimeMS: 0,
})),
});
}
const searchClient = algoliasearch(
'########',
'###################',
);
return searchClient.search(requests);
},
};
render() {
return (
<View>
<SecondaryHeader secondaryHeaderTitle="Add Buddies" />
<InstantSearch
searchClient={this.conditionalQuery}
indexName="prod_USERSCOLLECTION"
onSearchStateChange={searchState =>
console.log('=====> ', searchState)
}>
<ConnectedSearchBox />
<InfiniteHits navigation={this.props.navigation} />
</InstantSearch>
</View>
);
}
This solution can still be improved for the spaces typed, but I don't know how to do that.

add onPress to first component and call it on second component

I'm a beginner in react-native I want to make add ( + ) button on the header of the first component when user click on onPress it will create a new form and the form is the second component
In App.js SetUp Navigation like below:
import { createAppContainer, createStackNavigator } from "react-navigation";
import MainScreen from "./screens/MainScreen";
import SecondScreen from "./screens/SecondScreen";
const AppNavigator = createStackNavigator({
MainScreen: {
screen: MainScreen
},
SecondScreen: {
screen: SecondScreen,
},
})
const App = createAppContainer(AppNavigator);
export default App;
In Main Screen
import React, { Component } from "react";
import {
View,
Button,
} from "react-native";
class MainScreen extends Component {
handleOnPress = () => {
this.props.navigation.navigate("SecondScreen");
};
render() {
return (
<View>
<Button
onPress={this.handleOnPress}
title="Button"
color="#841584"
/>
</View>
);
}
}
export default MainScreen;
It will redirect you to second screen
You can set ref of Form component
<FormsComponent
ref={ref => {
this.formComponent = ref;
}}
/>
You should pass function as prop for Header component
<HeaderComponent addNewForm={this.addNewForm} />
Parent component will call function of form component when click in header component using ref
addNewForm = () => {
this.formComponent.addNewForm();
};
App Preview
Complete Code
import React, { Component } from "react";
import { View, Text, FlatList } from "react-native";
class HeaderComponent extends Component {
render() {
return (
<View style={{ height: 80, backgroundColor: "red", paddingTop: 24 }}>
<Text style={{ padding: 20 }} onPress={this.props.addNewForm}>
Call Function of Form Component
</Text>
</View>
);
}
}
class FormsComponent extends Component {
addNewForm = () => {
alert("add new form");
};
render() {
return (
<FlatList
data={[{ key: "Form1" }, { key: "Form2" }]}
renderItem={({ item }) => <Text>{item.key}</Text>}
style={{ flex: 1 }}
/>
);
}
}
export default class App extends Component {
addNewForm = () => {
this.formComponent.addNewForm();
};
render() {
return (
<View style={{ flex: 1 }}>
<HeaderComponent addNewForm={this.addNewForm} />
<FormsComponent
ref={ref => {
this.formComponent = ref;
}}
/>
</View>
);
}
}