React native render component based on TextInput onfocus - reactjs

I want to display something in my react component when user clicks into a text input (something similar to Instagram's search, where if you click on their input field, a search component suggestion shows up.
const SearchScreen = props => {
const renderSearch = () => {
return (
<>
// need to display the search suggestion
</>
)
}
return (
<>
<TextInput
placeholder="Search"
onChangeText={text => handleChange(text)}
value={searchText}
onFocus={() => renderSearch()} // based on focus, then render component
/>
<View>
// how do I render here?
// this will render on load, but need to render onFocus
{renderSearch}
</View>
</>
);
};

You can apply a similar pattern than stackoverflow.com/a/34091564/1839692.
For instance you can try something like :
const SearchScreen = props => {
const [searchFocus, setSearchFocus] = useState(false)
const renderSearch = () => {
return (
<>
// need to display the search suggestion
</>
)
}
return (
<>
<TextInput
placeholder="Search"
onChangeText={text => handleChange(text)}
value={searchText}
onFocus={() => setSearchFocus(true)}
onBlur={() => setSearchFocus(false)}
/>
{ searchFocus
? <View>
{renderSearch}
</View>
: <View>
// Add here the code to display when not searching
</View>
}
</>
);
};

Related

is there a way to avoid or improve re-rendering of contact list while searching for contacts in react native

I am working on a react-native contact app with realtime search functionality. I have a problem with the contact List re-rendering on every keypress. is there a way I can optimize the search function where I can avoid the unnecessary re-renders on contacts Flatlist. Thanks, in advance
here is my contact component:
const Contacts = () => {
const [contact, setContact] = useState({});
useEffect(() => {...read contacts and then save it to contact and inMemoryContact state});
const renderItem = ({ item }) => <RenderContacts item={item} />;
const renderList = () => {
return (
<FlatList
keyboardShouldPersistTaps="handled"
data={contact}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItem}
/>
);
};
return (
<View>
<Text style={Styles.textStyle}>All Contacts</Text>
{renderList()}
</View>
);
};
here is my search component:
const SearchBar = ({ updateContactState }) => {
const { inMemoryContact } = useSelector(state => state.contactReducer);
const searchContacts = value => {
const filteredContact = inMemoryContact.filter(contactToFilter => {
const contactLowerCase = `${contactToFilter.firstName} ${contactToFilter.lastName}`.toLowerCase();
const searchTerm = value.toLowerCase();
return contactLowerCase.indexOf(searchTerm) > -1;
});
updateContactState(filteredContact);
};
return (
<View style={Styles.SectionStyle}>
<MaterialIcons style={Styles.iconStyle} name="search" size={28} />
<TextInput
placeholder="Search Contact"
autoCapitalize="none"
autoCorrect={false}
onChangeText={content => {
searchContacts(content);
}}
/>
</View>
);
};
here is my renderContact component which keeps re-rendering:
const RenderContacts = ({ item }) => {
return item.phoneNumbers.map(element => (
<TouchableOpacity ...>
... list of contacts
</TouchableOpacity>
));
)
}
Your Problem Statement
"contact List re-rendering on every keypress"
Proposed Solution
Use debounce to delay the call. Here is a detailed article on what debounce does. Debounce will delay processing your input until a certain delay period. This will prevent the extra re-renders that can occur while a user is quickly typing in the search bar.
debounce function is readily available in most utils library including lodash.
Two solutions:
Use RenderContact as a function not JSX element
Bring it outside the scope of your main function
Let me know which one works for you.

Is setting a value attribute in TextInput necessary?

I was having issues with something like this (specifically in the TextInput value attribute):
const Stuff = props => {
const [items, setItems] = useState([]);
const handleNewItem = () => {
setItems([...items, '']);
};
const handleText = (text, index) => {
items[index] = text;
setItems(items);
// this was populating correctly in console.log
// as I type, it will come out like ["somedata", "blah"....] etc...
};
return (
<>
<View style={{marginTop: 20}}>
<View>
{items.map((items, index) => {
return (
<View key={index}>
<Text>{index + 1}</Text>
// issue with setting value attribute
// Text would disappear as I type in the input field
<TextInput value={items} onChangeText={text => handleText(text, index)} />
</View>
);
})}
<TouchableOpacity onPress={e => handleNewItem(e)}>
<Text>Add item</Text>
</TouchableOpacity>
</View>
</View>
</>
);
};
I was able to get console logged out the correct values for items, but on my mobile simulator, when I type something, the text disappears.
When I removed value={items} from the TextInput component, I'm able to type in the simulator input field, without the text disappearing. I always thought we needed a value from reactjs. Do we not need this? or am I doing something wrong?
I would suggest don't directly update your state. Instead use new object to update the state like
const handleText = (text, index) => {
let newItems = [...items];
newItems[index] = text;
setItems(newItems);
};

Exposing state props from functional component to navigationOptions function

I have a component that renders the input field, I want to pass the data to the next page when user clicks on "next" button in the header. What is the best practice for doing so? How do I expose this into Page.navigationOptions?
Or is it best to just set up redux for these types of things?
const Page = () => {
const [desc, getDesc] = useState('');
return (
<View style={styles.inputFieldDescContainer}>
<TextInput
multiline
placeholder='Write a description...'
onChangeText={(text) => getDesc(text)}
value={desc}
/>
</View>
);
};
// How do I pass desc properties down into navigationOptions?
Page.navigationOptions = (navData) => {
return {
headerTitle: 'Page,
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Next'
onPress={() => {
navData.navigation.navigate('NextPage', {data: navData});
}}
/>
</HeaderButtons>
),
headerBackTitle: null
};
};
/* NextPage.js */
const NextPage = (props) => {
console.log('enter props data', props.navigation.getParam('data'));
console.log('enter props navigation', props.navigation);
const [valueText, setValueText] = useState();
return (
<View>
<TextInput onChangeText={(text) => setValueText(text)} value={valueText}/>
<TouchableOpacity><Text>Create your workout</Text></TouchableOpacity>
</View>
);
;}
Sharing state and props between component and options is possible in React Navigation 5 https://blog.expo.io/announcing-react-navigation-5-0-bd9e5d45569e
In React Navigation 4, you can use params to store the value to be able to share it:
const Page = ({ navigation }) => {
const desc = navigation.getParam('description', '');
return (
<View style={styles.inputFieldDescContainer}>
<TextInput
multiline
placeholder='Write a description...'
onChangeText={(text) => navigation.setParams({ description: text )}
value={desc}
/>
</View>
);
}

How to clear TextInput on send button in React native with redux

I am working on chat app using react native with redux, where messages are sending through the send button. But whenever I sent a message on hitting the send button, the TextInput is not clearing.
I want to clear the TextInput field on hitting the send button. Here I am working in redux so I don't want to use state with value.
Here is the code :
class Chat extends Component {
componentWillMount() {
this.props.fetchChat(this.props.receiverId);
}
message(text) {
this.props.writeMsg(text);
}
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
}
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
inverted={-1}
style={styles.list}
extraData={this.props}
data={this.props.convo}
keyExtractor = {(item) => {
return item.id;
}}
renderItem=
<ChatItem value={this.renderItem} />
/>
<MessageInput
onChangeText={text => this.message(text)}
onPress={this.onSend }
/>
</View>
);
}
}
And this is the component MessageInput's code:
<View style={inputContainer}>
<TextInput style={inputs}
placeholder="Write a message..."
onChangeText={onChangeText}
/>
</View>
<TouchableOpacity style={btnSend} onPress={onPress }>
<Icon
name='send'
type='MaterialIcons'
color='#fff'
style={iconSend}
/>
</TouchableOpacity>
You can use a ref to clear the value from Chat.
Add a new ref inside your constructor
constructor(props) {
super(props);
this.textInput = React.createRef();
}
Pass the ref into MessageInput.
render() {
...
<MessageInput
onChangeText={text => this.message(text)}
onPress={this.onSend }
ref={this.textInput}
/>
...
}
Modify MessageInput (I am going to assume it's a functional component)
const MessageInput = (props, ref) => (
...
<TextInput style={inputs}
placeholder="Write a message..."
onChangeText={onChangeText}
ref={ref}
/>
...
)
Finally, switch back to the Chat component and update onSend
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
this.textInput.current.clear(); // Clear the text input
}
You can try with clearing the text property after the message is sended, (if the text property is what is rendered in the TextInput):
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
this.message('');
}
or
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
this.props.writeMsg('');
}

How to make autocomplete list in front of other components in react-native?

I use a RootForm as the basic template for form page. There is one field associated with location autocomplete, so I wrap the native autocomplete of react-native and use it in that field. However, the autocomplete dropdown list is blocked by other fields in the form which are rendered behind it. I try to search online but no useful materials. Using modal or zIndex is not the solution here. How could I make the dropdown list on the top of other components even if it renders earlier than other components?
The following two snippets are my rootform and autocomplete render functions.
render() {
const { input } = this.state;
const cities = this.state.cities;
return (
<View style={styles.container}>
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={cities}
defaultValue={input}
onChangeText={text => this.setState({ input: text })}
placeholder="Enter Location"
renderItem={({ cityName, cityId }) => (
<TouchableOpacity style={styles.listStyle} onPress={() => this.setState({ input: cityName, cities: [] })}>
<Text style={styles.itemText}>
{cityName}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
render() {
const data = this.props.data;
let fields = [];
let onPress = null;
Object.keys(data).forEach((key, index) => {
let options = data[key].options ?
data[key].options : null
if ("type" in data[key]) {
fields.push(
<View style={styles.formField} key={key}>
<Text style={styles.text}>{data[key].label}</Text>
<AutoComplete />
</View>
)
} else {
let custom = [styles.formField];
if (options) {
fields.push(
<View style={custom} key={key}>
<Text style={styles.text}>{data[key].label}</Text>
<TextInput value={data[key].value} style={styles.input}
readOnly
{...options} />
</View>
)
} else {
fields.push(
<View style={custom} key={key}>
<Text style={styles.text}>{data[key].label}</Text>
<TextInput value={data[key].value} style={styles.input}
onChangeText={(text) => this.props.onFieldChange(key, text)}
{...options} />
</View>
);
}
}
})
return (
<KeyboardAwareScrollView style={styles.container}>
{fields}
</KeyboardAwareScrollView>
)
}
You can just change your style.container to have a higher zIndex than whatever is appearing on top of it. However this will have the other items in the form appear behind the area reserved for the dropdown list, and render them unselectable.
If you want items underneath the Autocomplete component's area to still be interactive/selectable, you can use React.useState in order to have a dynamic zIndex property on your component.
const [componentZIndex, setComponentZIndex] = React.useState(1);
You will want your components behind the area reserved for the list to have a zIndex higher than 2 so that they are interactive.
Then you'll want to render your own input component so that you have access to the onFocus property. Luckily, the library you are using allows you to do this:
renderTextInput={() => (
<View style={styles.input}>
<TextInput
onFocus={() => setComponentZIndex(999)}
value={value}
/>
</View>
)}
This will bring the list to the top whenever the user is using the autocomplete component. The last thing to do is to make sure that you push it to the back once the user is no longer using it.
React.useEffect(() => {
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.removeListener('keyboardDidHide', _keyboardDidHide);
};
}, []);
const _keyboardDidHide = () => {
setComponentZIndex(1)
};

Resources